mirror of
https://github.com/kwsch/NHSE.git
synced 2026-09-13 13:45:36 -05:00
Add unit test for pattern replacement
The previous method was fine, not sure what the underlying issue is. The unit tests assert that the sequence is fully replaced.
This commit is contained in:
@@ -41,8 +41,6 @@ private static void ChangeToHostTown(this IVillagerOrigin visit, IVillagerOrigin
|
||||
{
|
||||
var hostTown = host.GetTownIdentity();
|
||||
var visitTown = visit.GetTownIdentity();
|
||||
if (hostTown.SequenceEqual(visitTown))
|
||||
return;
|
||||
visitData.ReplaceOccurrences(visitTown, hostTown);
|
||||
}
|
||||
|
||||
@@ -50,8 +48,6 @@ private static void ChangeToHostPlayer(this IVillagerOrigin visit, IVillagerOrig
|
||||
{
|
||||
var hostPlayer = host.GetPlayerIdentity();
|
||||
var visitPlayer = visit.GetPlayerIdentity();
|
||||
if (hostPlayer.SequenceEqual(visitPlayer))
|
||||
return;
|
||||
visitData.ReplaceOccurrences(visitPlayer, hostPlayer);
|
||||
}
|
||||
}
|
||||
@@ -7,67 +7,25 @@ namespace NHSE.Core;
|
||||
/// </summary>
|
||||
public static class ArrayUtil
|
||||
{
|
||||
//public static int ReplaceOccurrences(this Span<byte> array, ReadOnlySpan<byte> pattern, ReadOnlySpan<byte> swap)
|
||||
//{
|
||||
// int count = 0;
|
||||
// int ofs = 0;
|
||||
// while (true)
|
||||
// {
|
||||
// var index = array[ofs..].IndexOf(pattern);
|
||||
// if (index == -1)
|
||||
// return count;
|
||||
// ofs += index;
|
||||
//
|
||||
// swap.CopyTo(array[ofs..]);
|
||||
// ofs += swap.Length; // skip past swapped data
|
||||
// ++count;
|
||||
// }
|
||||
//}
|
||||
|
||||
public static int ReplaceOccurrences(this Span<byte> array, ReadOnlySpan<byte> pattern, ReadOnlySpan<byte> swap)
|
||||
{
|
||||
if (pattern.Length != swap.Length)
|
||||
return -1;
|
||||
if (pattern.SequenceEqual(swap))
|
||||
return 0;
|
||||
|
||||
int count = 0;
|
||||
int ofs = 0;
|
||||
while (true)
|
||||
{
|
||||
int ofs = IndexOfBytes(array, pattern);
|
||||
if (ofs == -1)
|
||||
var index = array[ofs..].IndexOf(pattern);
|
||||
if (index == -1)
|
||||
return count;
|
||||
ofs += index;
|
||||
|
||||
swap.CopyTo(array[ofs..]);
|
||||
ofs += swap.Length; // skip past swapped data
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a provided <see cref="pattern"/> within the supplied <see cref="array"/>.
|
||||
/// </summary>
|
||||
/// <param name="array">Array to look in</param>
|
||||
/// <param name="pattern">Pattern to look for</param>
|
||||
/// <param name="startIndex">Starting offset to look from</param>
|
||||
/// <param name="length">Amount of entries to look through</param>
|
||||
/// <returns>Index the pattern occurs at; if not found, returns -1.</returns>
|
||||
public static int IndexOfBytes(ReadOnlySpan<byte> array, ReadOnlySpan<byte> pattern, int startIndex = 0, int length = -1)
|
||||
{
|
||||
int len = pattern.Length;
|
||||
int endIndex = length > 0
|
||||
? startIndex + length
|
||||
: array.Length - len - startIndex;
|
||||
|
||||
endIndex = Math.Min(array.Length - pattern.Length, endIndex);
|
||||
|
||||
int i = startIndex;
|
||||
int j = 0;
|
||||
while (true)
|
||||
{
|
||||
if (pattern[j] != array[i + j])
|
||||
{
|
||||
if (++i == endIndex)
|
||||
return -1;
|
||||
j = 0;
|
||||
}
|
||||
else if (++j == len)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
131
NHSE.Tests/ArrayUtilTests.cs
Normal file
131
NHSE.Tests/ArrayUtilTests.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using NHSE.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace NHSE.Tests;
|
||||
|
||||
public class ArrayUtilTests
|
||||
{
|
||||
[Fact]
|
||||
public void ReplaceOccurrences_WhenPatternNotFound_ReturnsZero()
|
||||
{
|
||||
byte[] array = [0x01, 0x02, 0x03, 0x04, 0x05];
|
||||
byte[] pattern = [0xAA, 0xBB];
|
||||
byte[] swap = [0xCC, 0xDD];
|
||||
|
||||
var count = array.ReplaceOccurrences(pattern, swap);
|
||||
|
||||
count.Should().Be(0);
|
||||
array.Should().BeEquivalentTo([0x01, 0x02, 0x03, 0x04, 0x05]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaceOccurrences_WhenSingleOccurrence_ReplacesAndReturnsOne()
|
||||
{
|
||||
byte[] array = [0x01, 0xAA, 0xBB, 0x04, 0x05];
|
||||
byte[] pattern = [0xAA, 0xBB];
|
||||
byte[] swap = [0xCC, 0xDD];
|
||||
|
||||
var count = array.ReplaceOccurrences(pattern, swap);
|
||||
|
||||
count.Should().Be(1);
|
||||
array.Should().BeEquivalentTo([0x01, 0xCC, 0xDD, 0x04, 0x05]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaceOccurrences_WhenMultipleOccurrences_ReplacesAllAndReturnsCount()
|
||||
{
|
||||
byte[] array = [0xAA, 0xBB, 0x03, 0xAA, 0xBB, 0x06, 0xAA, 0xBB];
|
||||
byte[] pattern = [0xAA, 0xBB];
|
||||
byte[] swap = [0xCC, 0xDD];
|
||||
|
||||
var count = array.ReplaceOccurrences(pattern, swap);
|
||||
|
||||
count.Should().Be(3);
|
||||
array.Should().BeEquivalentTo([0xCC, 0xDD, 0x03, 0xCC, 0xDD, 0x06, 0xCC, 0xDD]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaceOccurrences_WhenConsecutiveOccurrences_ReplacesAll()
|
||||
{
|
||||
byte[] array = [0xAA, 0xBB, 0xAA, 0xBB, 0xAA, 0xBB];
|
||||
byte[] pattern = [0xAA, 0xBB];
|
||||
byte[] swap = [0xCC, 0xDD];
|
||||
|
||||
var count = array.ReplaceOccurrences(pattern, swap);
|
||||
|
||||
count.Should().Be(3);
|
||||
array.Should().BeEquivalentTo([0xCC, 0xDD, 0xCC, 0xDD, 0xCC, 0xDD]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaceOccurrences_WhenSwapContainsPattern_DoesNotCauseInfiniteLoop()
|
||||
{
|
||||
// Swap contains the original pattern - must skip past swapped data
|
||||
byte[] array = [0x01, 0xAA, 0xBB, 0x04];
|
||||
byte[] pattern = [0xAA, 0xBB];
|
||||
byte[] swap = [0xAA, 0xBB]; // Same as pattern
|
||||
|
||||
var count = array.ReplaceOccurrences(pattern, swap);
|
||||
|
||||
count.Should().Be(0);
|
||||
array.Should().BeEquivalentTo([0x01, 0xAA, 0xBB, 0x04]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaceOccurrences_WhenLargeFileWithRandomPlacements_ReplacesAllOccurrences()
|
||||
{
|
||||
const int fileSize = 1024 * 1024; // 1 MB
|
||||
const int sequenceLength = 0x13; // 19 bytes
|
||||
const int seed = 42;
|
||||
|
||||
var random = new Random(seed);
|
||||
Span<byte> file = new byte[fileSize];
|
||||
random.NextBytes(file);
|
||||
|
||||
// Create two distinct random sequences
|
||||
Span<byte> patternA = stackalloc byte[sequenceLength];
|
||||
Span<byte> patternB = stackalloc byte[sequenceLength];
|
||||
random.NextBytes(patternA);
|
||||
random.NextBytes(patternB);
|
||||
|
||||
// Ensure patterns are different
|
||||
patternB[0] = (byte)(patternA[0] ^ 0xFF);
|
||||
|
||||
// Insert pattern A at random non-overlapping offsets
|
||||
var insertedOffsets = new List<int>();
|
||||
int currentPosition = 0;
|
||||
|
||||
while (currentPosition + sequenceLength <= fileSize)
|
||||
{
|
||||
// Random gap between 0 and 1000 bytes, then add sequence length to avoid overlap
|
||||
int gap = random.Next(0, 1000);
|
||||
int nextOffset = currentPosition + gap;
|
||||
|
||||
if (nextOffset + sequenceLength > fileSize)
|
||||
break;
|
||||
|
||||
// Insert pattern A at this offset
|
||||
patternA.CopyTo(file[nextOffset..]);
|
||||
insertedOffsets.Add(nextOffset);
|
||||
|
||||
// Move past the inserted sequence
|
||||
currentPosition = nextOffset + sequenceLength;
|
||||
}
|
||||
|
||||
// Run the replacement
|
||||
var count = file.ReplaceOccurrences(patternA, patternB);
|
||||
|
||||
// Verify count matches
|
||||
count.Should().Be(insertedOffsets.Count);
|
||||
|
||||
// Verify each offset now contains pattern B
|
||||
foreach (var offset in insertedOffsets)
|
||||
{
|
||||
var actual = file.Slice(offset, sequenceLength);
|
||||
actual.SequenceEqual(patternB).Should().BeTrue(because: $"offset {offset} should have been replaced");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user