Skip to content

Commit

Permalink
Improve block code coverage with more tests (#205)
Browse files Browse the repository at this point in the history
  • Loading branch information
nietras authored Dec 1, 2024
1 parent ea1b165 commit 97ab8b6
Show file tree
Hide file tree
Showing 9 changed files with 242 additions and 7 deletions.
84 changes: 84 additions & 0 deletions src/Sep.Test/Internals/SepArrayPoolAccessIndexedTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace nietras.SeparatedValues.Test;

[TestClass]
public class SepArrayPoolAccessIndexedTest
{
readonly SepArrayPoolAccessIndexed _sut = new();

[TestMethod]
public void SepArrayPoolAccessIndexedTest_Ctor() { }

[TestMethod]
public void SepArrayPoolAccessIndexedTest_RentUniqueArrayAsSpan()
{
AssertUniqueSpans<int>();
AssertUniqueSpans<float>();
AssertUniqueSpans<double>();
AssertUniqueSpans<string>();
}

[TestMethod]
public void SepArrayPoolAccessIndexedTest_RentUniqueArrayAsSpan_Reset_RentUniqueArrayAsSpan_Same()
{
var spanInt0 = _sut.RentUniqueArrayAsSpan<int>(length: 4);
var spanFloat0 = _sut.RentUniqueArrayAsSpan<float>(length: 8);

_sut.Reset();

var spanInt1 = _sut.RentUniqueArrayAsSpan<int>(length: 4);
var spanFloat1 = _sut.RentUniqueArrayAsSpan<float>(length: 8);

AssertRef(spanInt0, spanInt1, areSame: true);
AssertRef(spanFloat0, spanFloat1, areSame: true);
}

[TestMethod]
public void SepArrayPoolAccessIndexedTest_RentUniqueArrayAsSpan_Reset_RentUniqueArrayAsSpan_LargerNotSame()
{
var spanInt0 = _sut.RentUniqueArrayAsSpan<int>(length: 4);

_sut.Reset();

var spanInt1 = _sut.RentUniqueArrayAsSpan<int>(length: 128);

Assert.AreEqual(128, spanInt1.Length);
AssertRef(spanInt0, spanInt1, areSame: false);
}

[TestMethod]
public void SepArrayPoolAccessIndexedTest_RentUniqueArrayAsSpan_Dispose()
{
SepArrayPoolAccessIndexedTest_RentUniqueArrayAsSpan();

_sut.Dispose();
}

void AssertUniqueSpans<T>()
{
var span4_0 = _sut.RentUniqueArrayAsSpan<T>(length: 4);
var span4_1 = _sut.RentUniqueArrayAsSpan<T>(length: 4);
var span8_0 = _sut.RentUniqueArrayAsSpan<T>(length: 8);
var span8_1 = _sut.RentUniqueArrayAsSpan<T>(length: 8);

Assert.AreEqual(4, span4_0.Length);
Assert.AreEqual(4, span4_1.Length);
Assert.AreEqual(8, span8_0.Length);
Assert.AreEqual(8, span8_1.Length);

AssertRef(span4_0, span4_1, areSame: false);
AssertRef(span4_0, span8_0, areSame: false);
AssertRef(span8_0, span8_1, areSame: false);
}

static void AssertRef<T>(Span<T> a, Span<T> b, bool areSame)
{
ref var refA = ref MemoryMarshal.GetReference(a);
ref var refB = ref MemoryMarshal.GetReference(b);
Assert.AreEqual(areSame, Unsafe.AreSame(ref refA, ref refB));
}
}
27 changes: 25 additions & 2 deletions src/Sep.Test/SepParserTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ public class SepParserTest

public SepParserTest()
{
_chars = new char[1024];
_colEndsOrColInfos = new int[1024];
_chars = new char[2048];
_colEndsOrColInfos = new int[2048];

_state._chars = _chars;
_state._colEndsOrColInfos = _colEndsOrColInfos;
Expand Down Expand Up @@ -224,6 +224,29 @@ public void SepParserTest_ParseColEnds_Long_ColEndsAlmostFilled(object parserObj
AssertOutput(parser, charsStart, charsEnd, expectedOutput);
}

[TestMethod]
[DynamicData(nameof(Parsers))]
public void SepParserTest_ParseColEnds_NewLinesOnly(object parserObject)
{
Contract.Assume(parserObject is not null);
var parser = (ISepParser)parserObject;
var charsEnd = _chars.Length - parser.PaddingLength;
_chars.AsSpan(0, charsEnd).Fill('\n');
_state._currentRowColCount = 0;
var parsedRowsLength = _state._parsedRows.Length;
Trace.WriteLine($"{nameof(parsedRowsLength)} {parsedRowsLength} {nameof(charsEnd)} {charsEnd}");
var rowCount = Math.Min(charsEnd, parsedRowsLength);
var charsStart = 0;
var lineNumberOffset = 4;
var expectedOutput = new ExpectedOutput(
Enumerable.Range(0, rowCount)
.Select(i => new ExpectedParsedRow(ColEnds: [Math.Max(0, i - 1), i], i + lineNumberOffset)).ToArray(),
new(ColEnds: [19], LineNumberTo: rowCount + lineNumberOffset - 1,
CharsStartIndex: rowCount, ColEndsStartIndex: rowCount * 2, ColCount: 0));

AssertOutput(parser, charsStart, charsEnd, expectedOutput);
}

int FillChars(string text)
{
text.AsSpan().CopyTo(_chars);
Expand Down
2 changes: 2 additions & 0 deletions src/Sep.Test/SepReaderColTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ public void SepReaderColTest_Unescape_Col_Test(string src, string expectedCol)

public static IEnumerable<object[]> TrimOuterNoQuotesData =>
[
[" ", ""],
[" ", ""],
["a", "a"],
[" a", "a"],
["a ", "a"],
Expand Down
88 changes: 88 additions & 0 deletions src/Sep.Test/SepReaderExtensionsEnumerationTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace nietras.SeparatedValues.Test;

[TestClass]
public class SepReaderExtensionsEnumerationTest
{
const int PartRowCount = 1000;
const int TotalRowCount = 2 * PartRowCount;
const string ColNameInc = "Inc";
const string ColNameFill = "Fill";
const string ColNameDec = "Dec";
const string ShortRowFill = "A";
static readonly string s_longRowFill = new('B', 256);
static readonly List<Seq> s_expected = Enumerable.Range(0, TotalRowCount)
.Select(i => new Seq(i, TotalRowCount - i)).ToList();

record struct Seq(int Inc, int Dec);

[TestMethod]
public void SepReaderExtensionsEnumerationTest_Enumerate_RowFunc()
{
using var reader = CreateReader();
var actual = reader.Enumerate(Parse).ToList();
CollectionAssert.AreEqual(s_expected, actual);
}

[TestMethod]
public void SepReaderExtensionsEnumerationTest_Enumerate_RowTryFunc()
{
using var reader = CreateReader();
var actual = reader.Enumerate<Seq>(TryParseEven).ToList();
var expected = s_expected.Where(s => s.Inc % 2 == 0).ToList();
CollectionAssert.AreEqual(expected, actual);
}

[TestMethod]
public void SepReaderExtensionsEnumerationTest_ParallelEnumerate_RowFunc()
{
using var reader = CreateReader();
var actual = reader.ParallelEnumerate(Parse).ToList();
CollectionAssert.AreEqual(s_expected, actual);
}

[TestMethod]
public void SepReaderExtensionsEnumerationTest_ParallelEnumerate_RowTryFunc()
{
using var reader = CreateReader();
var actual = reader.ParallelEnumerate<Seq>(TryParseEven).ToList();
var expected = s_expected.Where(s => s.Inc % 2 == 0).ToList();
CollectionAssert.AreEqual(expected, actual);
}

static SepReader CreateReader()
{
var sb = new StringBuilder(1024 * 1024);
using var stringWriter = new StringWriter(sb);
using (var writer = Sep.Writer().To(stringWriter))
{
foreach (var (inc, dec) in s_expected)
{
var fill = inc < PartRowCount ? ShortRowFill : s_longRowFill;
using var row = writer.NewRow();
row[ColNameInc].Format(inc);
row[ColNameFill].Set(fill);
row[ColNameDec].Format(dec);
}
}
var csv = sb.ToString();
// Force small initial buffer length even for Release, to force reader
// state swapping and array swapping with increasing row length for
// ParallelEnumerate.
return Sep.Reader(o => o with { InitialBufferLength = 128 }).FromText(csv);
}

static bool TryParseEven(SepReader.Row row, out Seq seq)
{
seq = Parse(row);
return seq.Inc % 2 == 0;
}

static Seq Parse(SepReader.Row row) =>
new(row[ColNameInc].Parse<int>(), row[ColNameDec].Parse<int>());
}
12 changes: 9 additions & 3 deletions src/Sep.Test/SepReaderFuzzTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,9 @@ static string GetTestText(Random random, TestRow[] rows)
{
var sb = new StringBuilder(1024 * 1024);
var previousNewLine = "";
foreach (var row in rows)
for (var rowIndex = 0; rowIndex < rows.Length; rowIndex++)
{
var row = rows[rowIndex];
// Use indexing
var cols = row.Cols;
for (var colIndex = 0; colIndex < cols.Length; colIndex++)
Expand All @@ -85,8 +86,13 @@ static string GetTestText(Random random, TestRow[] rows)
var newLine = RandomNewLine(random);
// Avoid a new line that does not end up actually being a new line
newLine = newLine == "\n" && previousNewLine == "\r" ? "\r\n" : newLine;
sb.Append(newLine);
previousNewLine = newLine;
// Randomly skip adding new line at end of "file"
var appendNewLine = rowIndex < (rows.Length - 1) || random.Next(0, 2) == 0;
if (appendNewLine)
{
sb.Append(newLine);
previousNewLine = newLine;
}
}
var text = sb.ToString();
return text;
Expand Down
18 changes: 18 additions & 0 deletions src/Sep.Test/SepReaderRowTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,24 @@ public void SepReaderRowTest_Row_DebugView()
}
}

[TestMethod]
public void SepReaderRowTest_Row_DebugView_NoHeader()
{
using var reader = Sep.Reader(o => o with { HasHeader = false }).FromText(_rowText);
reader.MoveNext();
var rowDebugView = new SepReader.Row.DebugView(reader.Current);
var cols = rowDebugView.Cols;
Assert.AreEqual(_cols, cols.Length);
for (var colIndex = 0; colIndex < cols.Length; colIndex++)
{
var col = cols[colIndex];
Assert.AreEqual(colIndex, col.ColIndex);
Assert.IsNull(col.ColName);
Assert.AreEqual(_colValues[colIndex], col.ColValue);
Assert.AreEqual($"{colIndex:D2}", col.ColIndexName);
}
}

static void AssertCols(ReadOnlySpan<string> expected, in SepReader.Cols cols)
{
Assert.AreEqual(expected.Length, cols.Count);
Expand Down
11 changes: 11 additions & 0 deletions src/Sep.Test/SepTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ public void SepTest_Ctor()
}
}

[TestMethod]
public void SepTest_Property()
{
var sep = new Sep();
foreach (var separator in _supportedSeparators)
{
sep = sep with { Separator = separator };
Assert.AreEqual(separator, sep.Separator);
}
}

[TestMethod]
public void SepTest_New()
{
Expand Down
5 changes: 4 additions & 1 deletion src/Sep/SepReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,10 @@ internal bool ParseNewRows()
}
else
{
Debug.Assert(_parser is not null);
var quoteCount = _parser.QuoteCount;
Unsafe.Add(ref Unsafe.As<int, SepColInfo>(ref MemoryMarshal.GetArrayDataReference(_colEndsOrColInfos)), colInfoIndex) =
new(_charsDataEnd, _parser?.QuoteCount ?? 0);
new(_charsDataEnd, quoteCount);
}
++_parsingLineNumber;
_parsedRows[_parsedRowsCount] = new(_parsingLineNumber, _parsingRowColCount);
Expand All @@ -311,6 +313,7 @@ internal bool ParseNewRows()

public string ToString(int index) => ToStringDefault(index);

[MemberNotNullWhen(false, nameof(_parser))]
bool EnsureInitializeAndReadData(bool endOfFile)
{
var nothingLeftToRead = FillAndMaybeDoubleCharsBuffer(_charsPaddingLength);
Expand Down
2 changes: 1 addition & 1 deletion src/Sep/SepReaderState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ internal SepReaderState(SepReader other)

internal void SwapParsedRowsTo(SepReaderState other)
{
A.Assert(_parsedRowIndex == 0);
A.Assert((_rowIndex == 0 && _hasHeader && _parsedRowIndex == 1) || _parsedRowIndex == 0);
A.Assert(_parsedRowIndex <= _parsedRowsCount);

other._parsedRowIndex = _parsedRowIndex;
Expand Down

0 comments on commit 97ab8b6

Please sign in to comment.