mirror of
https://github.com/CommunityToolkit/dotnet.git
synced 2026-10-20 22:26:51 +08:00
e88c524c3e
Updated all unit tests to consistently use the expected value as the first argument in Assert.AreEqual calls. This improves readability and aligns with best practices for test assertions.
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(expected, result, $"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(expected, result, $"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;
|
|
}
|
|
}
|