Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// 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.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using Xunit;

namespace Generic.Dictionary
{
public class DictionaryConcurrentAccessDetectionTests
{
private async Task DictionaryConcurrentAccessDetection<TKey, TValue>(Dictionary<TKey, TValue> dictionary, bool isValueType, object comparer, Action<Dictionary<TKey, TValue>> add, Action<Dictionary<TKey, TValue>> get, Action<Dictionary<TKey, TValue>> remove, Action<Dictionary<TKey, TValue>> removeOutParam)
{
Task task = Task.Factory.StartNew(() =>
{
// Get the Dictionary into a corrupted state, as if it had been corrupted by concurrent access.
// We this deterministically by clearing the _entries array using reflection;
// this means that every Entry struct has a 'next' field of zero, which causes the infinite loop
// that we want Dictionary to break out of
FieldInfo entriesType = dictionary.GetType().GetField("_entries", BindingFlags.NonPublic | BindingFlags.Instance);
Array entriesInstance = (Array)entriesType.GetValue(dictionary);
Array entryArray = (Array)Activator.CreateInstance(entriesInstance.GetType(), new object[] { ((IDictionary)dictionary).Count });
entriesType.SetValue(dictionary, entryArray);

Assert.Equal(comparer, dictionary.GetType().GetField("_comparer", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(dictionary));
Assert.Equal(isValueType, dictionary.GetType().GetGenericArguments()[0].IsValueType);
Assert.Equal("ThrowInvalidOperationException_ConcurrentOperationsNotSupported", Assert.Throws<InvalidOperationException>(() => add(dictionary)).TargetSite.Name);
Assert.Equal("ThrowInvalidOperationException_ConcurrentOperationsNotSupported", Assert.Throws<InvalidOperationException>(() => get(dictionary)).TargetSite.Name);
Assert.Equal("ThrowInvalidOperationException_ConcurrentOperationsNotSupported", Assert.Throws<InvalidOperationException>(() => remove(dictionary)).TargetSite.Name);
Assert.Equal("ThrowInvalidOperationException_ConcurrentOperationsNotSupported", Assert.Throws<InvalidOperationException>(() => removeOutParam(dictionary)).TargetSite.Name);
}, TaskCreationOptions.LongRunning);

// If Dictionary regresses, we do not want to hang here indefinitely
Assert.True((await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(60))) == task) && task.IsCompletedSuccessfully);
}

[Theory]
[InlineData(null)]
[InlineData(typeof(CustomEqualityComparerInt32ValueType))]
public async Task DictionaryConcurrentAccessDetection_ValueTypeKey(Type comparerType)
{
IEqualityComparer<int> customComparer = null;

Dictionary<int, int> dic = comparerType == null ?
new Dictionary<int, int>() :
new Dictionary<int, int>((customComparer = (IEqualityComparer<int>)Activator.CreateInstance(comparerType)));

dic.Add(1, 1);

await DictionaryConcurrentAccessDetection(dic,
typeof(int).IsValueType,
customComparer,
d => d.Add(1, 1),
d => { var v = d[1]; },
d => d.Remove(1),
d => d.Remove(1, out int value));
}

[Theory]
[InlineData(null)]
[InlineData(typeof(CustomEqualityComparerDummyRefType))]
public async Task DictionaryConcurrentAccessDetection_ReferenceTypeKey(Type comparerType)
{
IEqualityComparer<DummyRefType> customComparer = null;

Dictionary<DummyRefType, DummyRefType> dic = comparerType == null ?
new Dictionary<DummyRefType, DummyRefType>() :
new Dictionary<DummyRefType, DummyRefType>((customComparer = (IEqualityComparer<DummyRefType>)Activator.CreateInstance(comparerType)));

var keyValueSample = new DummyRefType() { Value = 1 };

dic.Add(keyValueSample, keyValueSample);

await DictionaryConcurrentAccessDetection(dic,
typeof(DummyRefType).IsValueType,
customComparer,
d => d.Add(keyValueSample, keyValueSample),
d => { var v = d[keyValueSample]; },
d => d.Remove(keyValueSample),
d => d.Remove(keyValueSample, out DummyRefType value));
}
}

// We use a custom type instead of string because string use optimized comparer https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Collections/Generic/Dictionary.cs#L79
// We want to test case with _comparer = null
class DummyRefType
{
public int Value { get; set; }
public override bool Equals(object obj)
{
return ((DummyRefType)obj).Equals(this.Value);
}

public override int GetHashCode()
{
return Value.GetHashCode();
}
}

class CustomEqualityComparerDummyRefType : EqualityComparer<DummyRefType>
{
public override bool Equals(DummyRefType x, DummyRefType y)
{
return x.Value == y.Value;
}

public override int GetHashCode(DummyRefType obj)
{
return obj.GetHashCode();
}
}

class CustomEqualityComparerInt32ValueType : EqualityComparer<int>
{
public override bool Equals(int x, int y)
{
return EqualityComparer<int>.Default.Equals(x, y);
}

public override int GetHashCode(int obj)
{
return EqualityComparer<int>.Default.GetHashCode(obj);
}
}
}
29 changes: 16 additions & 13 deletions src/System.Collections/tests/System.Collections.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,9 @@
<Compile Include="$(CommonTestPath)\System\Collections\IDictionary.NonGeneric.Tests.cs">
<Link>Common\System\Collections\IDictionary.NonGeneric.Tests.cs</Link>
</Compile>
<Compile Include="$(CommonTestPath)\System\Collections\IDictionary.NonGeneric.Tests.netcoreapp.cs" Condition="'$(TargetGroup)' == 'netcoreapp'">
<Link>Common\System\Collections\IDictionary.NonGeneric.Tests.netcoreapp.cs</Link>
</Compile>
<Compile Include="$(CommonTestPath)\System\Collections\IDictionary.Generic.Tests.cs">
<Link>Common\System\Collections\IDictionary.Generic.Tests.cs</Link>
</Compile>
<Compile Include="$(CommonTestPath)\System\Collections\IDictionary.Generic.Tests.netcoreapp.cs" Condition="'$(TargetGroup)' == 'netcoreapp'">
<Link>Common\System\Collections\IDictionary.Generic.Tests.netcoreapp.cs</Link>
</Compile>
<Compile Include="$(CommonTestPath)\System\Collections\IEnumerable.NonGeneric.Tests.cs">
<Link>Common\System\Collections\IEnumerable.NonGeneric.Tests.cs</Link>
</Compile>
Expand Down Expand Up @@ -77,14 +71,11 @@
<Link>Common\System\Runtime\Serialization\Formatters\BinaryFormatterHelpers.cs</Link>
</Compile>
<!-- Generic tests -->
<Compile Include="Generic\Dictionary\Dictionary.Generic.Tests.netcoreapp.cs" Condition="'$(TargetGroup)' == 'netcoreapp'" />
<Compile Include="Generic\SortedSet\SortedSet.TreeSubSet.Tests.cs" />
<Compile Include="StructuralComparisonsTests.cs" />
<Compile Include="BitArray\BitArray_CtorTests.cs" />
<Compile Include="BitArray\BitArray_GetSetTests.cs" />
<Compile Include="BitArray\BitArray_OperatorsTests.cs" />
<Compile Include="BitArray\BitArray_OperatorsTests.netcoreapp.cs" Condition="'$(TargetGroup)'=='netcoreapp'" />
<Compile Include="Generic\CollectionExtensionsTests.cs" Condition="'$(TargetGroup)'=='netcoreapp'" />
<Compile Include="Generic\Comparers\Comparer.Generic.Tests.cs" />
<Compile Include="Generic\Comparers\Comparer.Tests.cs" />
<Compile Include="Generic\Comparers\Comparers.Generic.cs" />
Expand All @@ -99,7 +90,6 @@
<Compile Include="Generic\Dictionary\Dictionary.Generic.Tests.Values.cs" />
<Compile Include="Generic\HashSet\HashSet.Generic.cs" />
<Compile Include="Generic\HashSet\HashSet.Generic.Tests.cs" />
<Compile Include="Generic\HashSet\HashSet.Generic.Tests.netcoreapp.cs" Condition="'$(TargetGroup)'=='netcoreapp'" />
<Compile Include="Generic\HashSet\HashSet.Generic.Tests.AsNonGenericIEnumerable.cs" />
<Compile Include="Generic\LinkedList\LinkedList.Generic.cs" />
<Compile Include="Generic\LinkedList\LinkedList.Generic.Tests.AddAfter.cs" />
Expand Down Expand Up @@ -131,7 +121,6 @@
<Compile Include="Generic\List\List.Generic.Tests.Sort.cs" />
<Compile Include="Generic\Queue\Queue.Generic.cs" />
<Compile Include="Generic\Queue\Queue.Generic.Tests.cs" />
<Compile Include="Generic\Queue\Queue.Generic.Tests.netcoreapp.cs" Condition="'$(TargetGroup)'=='netcoreapp'" />
<Compile Include="Generic\Queue\Queue.Tests.cs" />
<Compile Include="Generic\SortedDictionary\SortedDictionary.Generic.cs" />
<Compile Include="Generic\SortedDictionary\SortedDictionary.Generic.Tests.cs" />
Expand All @@ -145,12 +134,10 @@
<Compile Include="Generic\SortedList\SortedList.Generic.Tests.cs" />
<Compile Include="Generic\SortedSet\SortedSet.Generic.cs" />
<Compile Include="Generic\SortedSet\SortedSet.Generic.Tests.cs" />
<Compile Include="Generic\SortedSet\SortedSet.Generic.Tests.netcoreapp.cs" Condition="'$(TargetGroup)'=='netcoreapp'" />
<Compile Include="Generic\SortedSet\SortedSet.Tests.cs" />
<Compile Include="Generic\Stack\Stack.Tests.cs" />
<Compile Include="Generic\Stack\Stack.Generic.cs" />
<Compile Include="Generic\Stack\Stack.Generic.Tests.cs" />
<Compile Include="Generic\Stack\Stack.Generic.Tests.netcoreapp.cs" Condition="'$(TargetGroup)'=='netcoreapp'" />
<Compile Include="$(CommonTestPath)\System\Diagnostics\DebuggerAttributes.cs">
<Link>Common\System\Diagnostics\DebuggerAttributes.cs</Link>
</Compile>
Expand All @@ -162,6 +149,22 @@
<Link>Common\System\Collections\IEnumerable.Generic.Serialization.Tests.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup Condition="'$(TargetGroup)'=='netcoreapp'">
<Compile Include="BitArray\BitArray_OperatorsTests.netcoreapp.cs" />
<Compile Include="Generic\CollectionExtensionsTests.cs" />
<Compile Include="Generic\Dictionary\Dictionary.Generic.Tests.netcoreapp.cs" />
<Compile Include="Generic\Dictionary\Dictionary.Generic.Tests.ConcurrentAccessDetection.netcoreapp.cs" />
<Compile Include="Generic\HashSet\HashSet.Generic.Tests.netcoreapp.cs" />
<Compile Include="Generic\Stack\Stack.Generic.Tests.netcoreapp.cs" />
<Compile Include="Generic\SortedSet\SortedSet.Generic.Tests.netcoreapp.cs" />
<Compile Include="Generic\Queue\Queue.Generic.Tests.netcoreapp.cs" />
<Compile Include="$(CommonTestPath)\System\Collections\IDictionary.Generic.Tests.netcoreapp.cs" >
<Link>Common\System\Collections\IDictionary.Generic.Tests.netcoreapp.cs</Link>
</Compile>
<Compile Include="$(CommonTestPath)\System\Collections\IDictionary.NonGeneric.Tests.netcoreapp.cs" >
<Link>Common\System\Collections\IDictionary.NonGeneric.Tests.netcoreapp.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\$(AssemblyName).rd.xml" />
</ItemGroup>
Expand Down