UniqueItemsConstraint
UniqueItemsConstraint tests whether all the items in a
collection are unique.
using NUnit.Framework.Internal;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace NUnit.Framework.Constraints
{
[System.Runtime.CompilerServices.NullableContext(1)]
[System.Runtime.CompilerServices.Nullable(0)]
public class UniqueItemsConstraint : CollectionItemsEqualConstraint
{
[System.Runtime.CompilerServices.NullableContext(0)]
private sealed class NUnitStringEqualityComparer : IEqualityComparer<string>
{
private readonly bool _ignoreCase;
public NUnitStringEqualityComparer(bool ignoreCase)
{
_ignoreCase = ignoreCase;
}
[System.Runtime.CompilerServices.NullableContext(2)]
public bool Equals(string x, string y)
{
StringComparison comparisonType = _ignoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.Ordinal;
return string.Equals(x, y, comparisonType);
}
[System.Runtime.CompilerServices.NullableContext(1)]
public int GetHashCode(string obj)
{
if (obj == null)
return 0;
if (_ignoreCase)
return StringComparer.CurrentCultureIgnoreCase.GetHashCode(obj);
return obj.GetHashCode();
}
}
[System.Runtime.CompilerServices.Nullable(0)]
internal sealed class UniqueItemsConstraintResult : ConstraintResult
{
internal ICollection NonUniqueItems { get; }
public UniqueItemsConstraintResult(IConstraint constraint, [System.Runtime.CompilerServices.Nullable(2)] object actualValue, ICollection nonUniqueItems)
: base(constraint, actualValue, nonUniqueItems.Count == 0)
{
NonUniqueItems = nonUniqueItems;
}
public override void WriteAdditionalLinesTo(MessageWriter writer)
{
if (base.Status == ConstraintStatus.Failure) {
writer.Write(" Not unique items: ");
string value = MsgUtils.FormatCollection(NonUniqueItems, 0, 10);
writer.WriteLine(value);
}
}
}
private static readonly MethodInfo ItemsUniqueMethod = typeof(UniqueItemsConstraint).GetMethod("ItemsUnique", BindingFlags.Static | BindingFlags.NonPublic);
private static readonly MethodInfo ItemsCastMethod = typeof(Enumerable).GetMethod("Cast", BindingFlags.Static | BindingFlags.Public);
public override string Description => "all items unique";
protected override bool Matches(IEnumerable actual)
{
ICollection nonUniqueItems = GetNonUniqueItems(actual);
return nonUniqueItems.Count == 0;
}
public override ConstraintResult ApplyTo<[System.Runtime.CompilerServices.Nullable(2)] TActual>(TActual actual)
{
IEnumerable actual2 = ConstraintUtils.RequireActual<IEnumerable>(actual, "actual", false);
ICollection nonUniqueItems = GetNonUniqueItems(actual2);
return new UniqueItemsConstraintResult(this, actual, nonUniqueItems);
}
private ICollection OriginalAlgorithm(IEnumerable actual)
{
List<object> list = new List<object>();
List<object> list2 = new List<object>();
foreach (object item in actual) {
bool flag = true;
bool flag2 = false;
foreach (object item2 in list2) {
if (ItemsEqual(item, item2)) {
flag = false;
flag2 = !list.Any((object o2) => ItemsEqual(item, o2));
break;
}
}
if (flag)
list2.Add(item);
else if (flag2) {
list.Add(item);
if (list.Count == 10)
return list;
}
}
return list;
}
[return: System.Runtime.CompilerServices.Nullable(2)]
private ICollection TryInferFastPath(IEnumerable actual)
{
List<Type> list = new List<Type>();
List<object> list2 = new List<object>();
foreach (object item in actual) {
list2.Add(item);
if (item != null)
list.Add(item.GetType());
}
if (list.Count == 0)
return Array.Empty<object>();
List<Type> list3 = list.Distinct().ToList();
if (list3.Count == 1) {
Type type = list3[0];
if (IsTypeSafeForFastPath(type)) {
MethodInfo methodInfo = ItemsCastMethod.MakeGenericMethod(type);
object[] parameters = new IEnumerable[1] {
actual
};
object obj = methodInfo.Invoke(null, parameters);
if (base.IgnoringCase) {
if (type == typeof(string))
return (ICollection)StringsUniqueIgnoringCase((IEnumerable<string>)obj);
if (type == typeof(char))
return (ICollection)CharsUniqueIgnoringCase((IEnumerable<char>)obj);
}
return (ICollection)ItemsUniqueMethod.MakeGenericMethod(type).Invoke(null, new object[1] {
obj
});
}
} else if (list3.All(delegate(Type o) {
if (IsTypeSafeForFastPath(o))
return !IsSpecialComparisonType(o);
return false;
})) {
return (ICollection)ItemsUnique(list2);
}
return null;
}
private static bool IsSpecialComparisonType(Type type)
{
if (type.IsGenericType)
return type.FullName().StartsWith("System.Collections.Generic.KeyValuePair`2", StringComparison.Ordinal);
if (Numerics.IsNumericType(type))
return true;
if (!(type == typeof(string)) && !(type == typeof(char)) && !(type == typeof(DateTimeOffset)))
return type == typeof(DictionaryEntry);
return true;
}
private ICollection GetNonUniqueItems(IEnumerable actual)
{
if (base.UsingExternalComparer)
return OriginalAlgorithm(actual);
Type genericTypeArgument = GetGenericTypeArgument(actual);
if ((object)genericTypeArgument == null)
return TryInferFastPath(actual) ?? OriginalAlgorithm(actual);
if (!IsTypeSafeForFastPath(genericTypeArgument))
return OriginalAlgorithm(actual);
if (base.IgnoringCase) {
if (genericTypeArgument == typeof(string))
return (ICollection)StringsUniqueIgnoringCase((IEnumerable<string>)actual);
if (genericTypeArgument == typeof(char))
return (ICollection)CharsUniqueIgnoringCase((IEnumerable<char>)actual);
}
return (ICollection)ItemsUniqueMethod.MakeGenericMethod(genericTypeArgument).Invoke(null, new object[1] {
actual
});
}
[System.Runtime.CompilerServices.NullableContext(2)]
private static bool IsTypeSafeForFastPath(Type type)
{
if ((object)type != null && type.IsSealed)
return !IsHandledSpeciallyByNUnit(type);
return false;
}
private static ICollection<T> ItemsUnique<[System.Runtime.CompilerServices.Nullable(2)] T>(IEnumerable<T> actual)
{
return NonUniqueItemsInternal(actual, EqualityComparer<T>.Default);
}
private ICollection<string> StringsUniqueIgnoringCase(IEnumerable<string> actual)
{
return NonUniqueItemsInternal(actual, new NUnitStringEqualityComparer(base.IgnoringCase));
}
private ICollection<char> CharsUniqueIgnoringCase(IEnumerable<char> actual)
{
ICollection<string> source = NonUniqueItemsInternal(from x in actual
select x.ToString(), new NUnitStringEqualityComparer(base.IgnoringCase));
return (from x in source
select x[0]).ToList();
}
private static ICollection<T> NonUniqueItemsInternal<[System.Runtime.CompilerServices.Nullable(2)] T>(IEnumerable<T> actual, IEqualityComparer<T> comparer)
{
HashSet<T> hashSet = new HashSet<T>(comparer);
HashSet<T> hashSet2 = new HashSet<T>(comparer);
List<T> list = new List<T>();
foreach (T item in actual) {
if (!hashSet.Add(item) && hashSet2.Add(item)) {
list.Add(item);
if (list.Count == 10)
return list;
}
}
return list;
}
private static bool IsHandledSpeciallyByNUnit(Type type)
{
if (type == typeof(string))
return false;
if (!type.IsArray && !typeof(IEnumerable).IsAssignableFrom(type) && !typeof(Stream).IsAssignableFrom(type) && !typeof(DirectoryInfo).IsAssignableFrom(type) && !(type.FullName == "System.Tuple"))
return type.FullName == "System.ValueTuple";
return true;
}
[return: System.Runtime.CompilerServices.Nullable(2)]
private static Type GetGenericTypeArgument(IEnumerable actual)
{
Type[] interfaces = actual.GetType().GetInterfaces();
foreach (Type type in interfaces) {
if (type.FullName().StartsWith("System.Collections.Generic.IEnumerable`1", StringComparison.Ordinal))
return type.GenericTypeArguments[0];
}
return null;
}
}
}