mirror of
https://github.com/chylex/.NET-Community-Toolkit.git
synced 2024-11-24 07:42:45 +01:00
260 lines
8.7 KiB
C#
260 lines
8.7 KiB
C#
// Licensed to the .NET Foundation under one or more agreements.
|
|
// The .NET Foundation licenses this file to you under the MIT license.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
using System;
|
|
using System.Runtime.InteropServices;
|
|
using CommunityToolkit.HighPerformance.UnitTests.Buffers.Internals;
|
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
|
|
namespace CommunityToolkit.HighPerformance.UnitTests.Extensions;
|
|
|
|
public partial class Test_ReadOnlySpanExtensions
|
|
{
|
|
/// <summary>
|
|
/// Gets the list of counts to test the extension for
|
|
/// </summary>
|
|
private static ReadOnlySpan<int> TestCounts => new[] { 0, 1, 7, 128, 255, 256, short.MaxValue, short.MaxValue + 1, 123_938, 1_678_922, 71_890_819 };
|
|
|
|
[TestMethod]
|
|
public void Test_ReadOnlySpanExtensions_RandomCount8()
|
|
{
|
|
TestForType((byte)123, CreateRandomData);
|
|
TestForType((sbyte)123, CreateRandomData);
|
|
TestForType(true, CreateRandomData);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Test_ReadOnlySpanExtensions_RandomCount16()
|
|
{
|
|
TestForType((ushort)4712, CreateRandomData);
|
|
TestForType((short)4712, CreateRandomData);
|
|
TestForType((char)4712, CreateRandomData);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Test_ReadOnlySpanExtensions_RandomCount32()
|
|
{
|
|
TestForType((int)37438941, CreateRandomData);
|
|
TestForType((uint)37438941, CreateRandomData);
|
|
TestForType((float)Math.PI, CreateRandomData);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Test_ReadOnlySpanExtensions_RandomCount64()
|
|
{
|
|
TestForType((long)47128480128401, CreateRandomData);
|
|
TestForType((ulong)47128480128401, CreateRandomData);
|
|
TestForType(Math.PI, CreateRandomData);
|
|
}
|
|
|
|
#if NET6_0_OR_GREATER
|
|
[TestMethod]
|
|
public void Test_ReadOnlySpanExtensions_RandomCountPtr()
|
|
{
|
|
TestForType(nint.MaxValue / 2, CreateRandomData);
|
|
TestForType(nuint.MaxValue / 2, CreateRandomData);
|
|
}
|
|
#endif
|
|
|
|
[TestMethod]
|
|
public void Test_ReadOnlySpanExtensions_RandomCountManaged()
|
|
{
|
|
Int? value = new(37438941);
|
|
|
|
// We can skip the most expensive test in this case, as we're not testing
|
|
// a SIMD enabled path. The last test requires a very high memory usage which
|
|
// sometimes causes the CI test runner to fail with an out of memory exception.
|
|
// Since we don't need to double check overflows in the managed case, which is
|
|
// just a classic linear loop with some optimizations, omitting this case is fine.
|
|
foreach (int count in TestCounts.Slice(0, TestCounts.Length - 1))
|
|
{
|
|
Random? random = new(count);
|
|
|
|
Int[] data = new Int[count];
|
|
|
|
foreach (ref Int item in data.AsSpan())
|
|
{
|
|
item = new Int(random.Next());
|
|
}
|
|
|
|
// Fill at least 20% of the items with a matching value
|
|
int minimum = count / 20;
|
|
|
|
for (int i = 0; i < minimum; i++)
|
|
{
|
|
data[random.Next(0, count)] = value;
|
|
}
|
|
|
|
int result = data.Count(value);
|
|
int expected = CountWithForeach(data, value);
|
|
|
|
Assert.AreEqual(result, expected, $"Failed {typeof(Int)} test with count {count}: got {result} instead of {expected}");
|
|
}
|
|
}
|
|
|
|
// Dummy type to test the managed code path of the API
|
|
private sealed class Int : IEquatable<Int>
|
|
{
|
|
private int Value { get; }
|
|
|
|
public Int(int value) => Value = value;
|
|
|
|
public bool Equals(Int? other)
|
|
{
|
|
if (other is null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (ReferenceEquals(this, other))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return this.Value == other.Value;
|
|
}
|
|
|
|
public override bool Equals(object? obj)
|
|
{
|
|
return ReferenceEquals(this, obj) || (obj is Int other && Equals(other));
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return this.Value;
|
|
}
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Test_ReadOnlySpanExtensions_FilledCount8()
|
|
{
|
|
TestForType((byte)123, (count, _) => CreateFilledData(count, (byte)123));
|
|
TestForType((sbyte)123, (count, _) => CreateFilledData(count, (sbyte)123));
|
|
TestForType(true, (count, _) => CreateFilledData(count, true));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Test_ReadOnlySpanExtensions_FilledCount16()
|
|
{
|
|
TestForType((ushort)4712, (count, _) => CreateFilledData(count, (ushort)4712));
|
|
TestForType((short)4712, (count, _) => CreateFilledData(count, (short)4712));
|
|
TestForType((char)4712, (count, _) => CreateFilledData(count, (char)4712));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Test_ReadOnlySpanExtensions_FilledCount32()
|
|
{
|
|
TestForType((int)37438941, (count, _) => CreateFilledData(count, (int)37438941));
|
|
TestForType((uint)37438941, (count, _) => CreateFilledData(count, (uint)37438941));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Test_ReadOnlySpanExtensions_FilledCount64()
|
|
{
|
|
TestForType((long)47128480128401, (count, _) => CreateFilledData(count, (long)47128480128401));
|
|
TestForType((ulong)47128480128401, (count, _) => CreateFilledData(count, (ulong)47128480128401));
|
|
}
|
|
|
|
#if NET6_0_OR_GREATER
|
|
[TestMethod]
|
|
public void Test_ReadOnlySpanExtensions_FilledCountPtr()
|
|
{
|
|
TestForType((nint)37438941, (count, _) => CreateFilledData(count, (nint)37438941));
|
|
TestForType((nuint)37438941, (count, _) => CreateFilledData(count, (nuint)37438941));
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Performs a test for a specified type.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type to test.</typeparam>
|
|
/// <param name="value">The target value to look for.</param>
|
|
/// <param name="provider">The function to use to create random data.</param>
|
|
private static void TestForType<T>(T value, Func<int, T, UnmanagedSpanOwner<T>> provider)
|
|
where T : unmanaged, IEquatable<T>
|
|
{
|
|
foreach (int count in TestCounts)
|
|
{
|
|
using UnmanagedSpanOwner<T> data = provider(count, value);
|
|
|
|
int result = data.GetSpan().Count(value);
|
|
int expected = CountWithForeach(data.GetSpan(), value);
|
|
|
|
Assert.AreEqual(result, expected, $"Failed {typeof(T)} test with count {count}: got {result} instead of {expected}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Counts the number of occurrences of a given value into a target <see cref="ReadOnlySpan{T}"/> instance.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of items to count.</typeparam>
|
|
/// <param name="span">The input <see cref="ReadOnlySpan{T}"/> instance to read.</param>
|
|
/// <param name="value">The value to look for.</param>
|
|
/// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="span"/>.</returns>
|
|
private static int CountWithForeach<T>(ReadOnlySpan<T> span, T value)
|
|
where T : IEquatable<T>
|
|
{
|
|
int count = 0;
|
|
|
|
foreach (T? item in span)
|
|
{
|
|
if (item.Equals(value))
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a random <typeparamref name="T"/> array filled with random data.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of items to put in the array.</typeparam>
|
|
/// <param name="count">The number of array items to create.</param>
|
|
/// <param name="value">The value to look for.</param>
|
|
/// <returns>An array of random <typeparamref name="T"/> elements.</returns>
|
|
private static UnmanagedSpanOwner<T> CreateRandomData<T>(int count, T value)
|
|
where T : unmanaged
|
|
{
|
|
Random? random = new(count);
|
|
|
|
UnmanagedSpanOwner<T> data = new(count);
|
|
|
|
foreach (ref byte n in MemoryMarshal.AsBytes(data.GetSpan()))
|
|
{
|
|
n = (byte)random.Next(0, byte.MaxValue);
|
|
}
|
|
|
|
// Fill at least 20% of the items with a matching value
|
|
int minimum = count / 20;
|
|
|
|
Span<T> span = data.GetSpan();
|
|
|
|
for (int i = 0; i < minimum; i++)
|
|
{
|
|
span[random.Next(0, count)] = value;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a <typeparamref name="T"/> array filled with a given value.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of items to put in the array.</typeparam>
|
|
/// <param name="count">The number of array items to create.</param>
|
|
/// <param name="value">The value to use to populate the array.</param>
|
|
/// <returns>An array of <typeparamref name="T"/> elements.</returns>
|
|
private static UnmanagedSpanOwner<T> CreateFilledData<T>(int count, T value)
|
|
where T : unmanaged
|
|
{
|
|
UnmanagedSpanOwner<T> data = new(count);
|
|
|
|
data.GetSpan().Fill(value);
|
|
|
|
return data;
|
|
}
|
|
}
|