From 346377965a7b66ca285699bb2b26a4a27d52128b Mon Sep 17 00:00:00 2001
From: Medeni Baykal <433724+Haplois@users.noreply.github.com>
Date: Thu, 15 Apr 2021 03:38:31 +0200
Subject: [PATCH 1/2] Hierarchy output is added to ManagedName APIs.
---
.../ManagedNameHelper.Reflection.cs | 57 +++++++++++++++++--
.../ManagedNameRoundTripTests.cs | 2 +-
2 files changed, 54 insertions(+), 5 deletions(-)
diff --git a/src/Microsoft.TestPlatform.AdapterUtilities/ManagedNameUtilities/ManagedNameHelper.Reflection.cs b/src/Microsoft.TestPlatform.AdapterUtilities/ManagedNameUtilities/ManagedNameHelper.Reflection.cs
index cfd2b08f4c..d7dc917413 100644
--- a/src/Microsoft.TestPlatform.AdapterUtilities/ManagedNameUtilities/ManagedNameHelper.Reflection.cs
+++ b/src/Microsoft.TestPlatform.AdapterUtilities/ManagedNameUtilities/ManagedNameHelper.Reflection.cs
@@ -47,6 +47,42 @@ public static partial class ManagedNameHelper
/// the RFC.
///
public static void GetManagedName(MethodBase method, out string managedTypeName, out string managedMethodName)
+ => GetManagedName(method, out managedTypeName, out managedMethodName, out _);
+
+ ///
+ /// Gets fully qualified managed type and method name from given instance.
+ ///
+ ///
+ /// A instance to get fully qualified managed type and method name.
+ ///
+ ///
+ /// When this method returns, contains the fully qualified managed type name of the .
+ /// This parameter is passed uninitialized; any value originally supplied in result will be overwritten.
+ /// The format is defined in the RFC.
+ ///
+ ///
+ /// When this method returns, contains the fully qualified managed method name of the .
+ /// This parameter is passed uninitialized; any value originally supplied in result will be overwritten.
+ /// The format is defined in the RFC.
+ ///
+ ///
+ /// When this method returns, contains the default test hierarchy values of the .
+ /// This parameter is passed uninitialized; any value originally supplied in result will be overwritten.
+ ///
+ ///
+ /// is null.
+ ///
+ ///
+ /// must describe a method.
+ ///
+ ///
+ /// Required functionality on is missing on the current platform.
+ ///
+ ///
+ /// More information about and can be found in
+ /// the RFC.
+ ///
+ public static void GetManagedName(MethodBase method, out string managedTypeName, out string managedMethodName, out string[] hierarchyValues)
{
if (method == null)
{
@@ -84,7 +120,7 @@ public static void GetManagedName(MethodBase method, out string managedTypeName,
var methodBuilder = new StringBuilder();
// Namespace and Type Name (with arity designation)
- AppendTypeString(typeBuilder, semanticType, closedType: false);
+ var hierarchyPos = AppendTypeString(typeBuilder, semanticType, closedType: false);
// Method Name with method arity
var arity = method.GetGenericArguments().Length;
@@ -111,6 +147,10 @@ public static void GetManagedName(MethodBase method, out string managedTypeName,
managedTypeName = typeBuilder.ToString();
managedMethodName = methodBuilder.ToString();
+ hierarchyValues = new[] {
+ managedTypeName.Substring(hierarchyPos[0], hierarchyPos[1] - hierarchyPos[0]),
+ managedTypeName.Substring(hierarchyPos[1] + 1, hierarchyPos[2] - hierarchyPos[1] - 1),
+ };
}
///
@@ -233,11 +273,13 @@ bool filter(MemberInfo mbr, object param)
#endif
}
- private static void AppendTypeString(StringBuilder b, Type type, bool closedType)
+ private static int[] AppendTypeString(StringBuilder b, Type type, bool closedType)
{
+ int[] hierarchies = null;
+
if (type.IsArray)
{
- AppendTypeString(b, type.GetElementType(), closedType);
+ hierarchies = AppendTypeString(b, type.GetElementType(), closedType);
b.Append('[');
for (int i = 0; i < type.GetArrayRank() - 1; i++)
{
@@ -256,16 +298,23 @@ private static void AppendTypeString(StringBuilder b, Type type, bool closedType
}
else
{
+ hierarchies = new int[3];
+ hierarchies[0] = b.Length;
+
AppendNamespace(b, type.Namespace);
+ hierarchies[1] = b.Length;
+
b.Append('.');
AppendNestedTypeName(b, type);
-
if (closedType)
{
AppendGenericTypeParameters(b, type);
}
+ hierarchies[2] = b.Length;
}
+
+ return hierarchies;
}
private static void AppendNamespace(StringBuilder b, string namespaceString)
diff --git a/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/ManagedNameUtilities/ManagedNameRoundTripTests.cs b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/ManagedNameUtilities/ManagedNameRoundTripTests.cs
index 12bb5cd280..c6bfc854fa 100644
--- a/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/ManagedNameUtilities/ManagedNameRoundTripTests.cs
+++ b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/ManagedNameUtilities/ManagedNameRoundTripTests.cs
@@ -839,7 +839,7 @@ private void VerifyRoundTripFromMethodInfo(
string expectedManagedMethodName)
{
// Generate the fqn for the Reflection MethodInfo
- ManagedNameHelper.GetManagedName(methodInfo, out var managedTypeName, out var managedMethodName);
+ ManagedNameHelper.GetManagedName(methodInfo, out var managedTypeName, out var managedMethodName, out var hierarchyValues);
Assert.AreEqual(expectedManagedTypeName, managedTypeName);
Assert.AreEqual(expectedManagedMethodName, managedMethodName);
From e72f7c431cc8230504db8eea52fdf45072ca32b2 Mon Sep 17 00:00:00 2001
From: Medeni Baykal <433724+Haplois@users.noreply.github.com>
Date: Thu, 15 Apr 2021 09:00:11 +0200
Subject: [PATCH 2/2] Updated AdapterUtilities
---
.../Friends.cs | 6 +
.../HierarchyConstants.cs | 12 +-
.../TestIdProvider.cs | 382 ++++++++++++++++++
.../TestIdProvider/CompatibilityTests.cs | 139 +++++++
.../TestIdProvider/SHA1ImplTests.cs | 173 ++++++++
5 files changed, 701 insertions(+), 11 deletions(-)
create mode 100644 src/Microsoft.TestPlatform.AdapterUtilities/Friends.cs
create mode 100644 src/Microsoft.TestPlatform.AdapterUtilities/TestIdProvider.cs
create mode 100644 test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/TestIdProvider/CompatibilityTests.cs
create mode 100644 test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/TestIdProvider/SHA1ImplTests.cs
diff --git a/src/Microsoft.TestPlatform.AdapterUtilities/Friends.cs b/src/Microsoft.TestPlatform.AdapterUtilities/Friends.cs
new file mode 100644
index 0000000000..17825fd742
--- /dev/null
+++ b/src/Microsoft.TestPlatform.AdapterUtilities/Friends.cs
@@ -0,0 +1,6 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.TestPlatform.AdapterUtilities.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
\ No newline at end of file
diff --git a/src/Microsoft.TestPlatform.AdapterUtilities/HierarchyConstants.cs b/src/Microsoft.TestPlatform.AdapterUtilities/HierarchyConstants.cs
index 71eaba0d9d..1546637c7c 100644
--- a/src/Microsoft.TestPlatform.AdapterUtilities/HierarchyConstants.cs
+++ b/src/Microsoft.TestPlatform.AdapterUtilities/HierarchyConstants.cs
@@ -26,7 +26,7 @@ public static class Levels
///
/// Total length of Hierarchy array.
///
- public const int TotalLevelCount = 4;
+ public const int TotalLevelCount = 2;
///
/// Index of the namespace element of the array.
@@ -37,16 +37,6 @@ public static class Levels
/// Index of the class element of the array.
///
public const int ClassIndex = 1;
-
- ///
- /// Index of the test group element of the array.
- ///
- public const int TestGroupIndex = 2;
-
- ///
- /// Index of the display name element of the array.
- ///
- public const int DisplayNameIndex = 3;
}
}
}
diff --git a/src/Microsoft.TestPlatform.AdapterUtilities/TestIdProvider.cs b/src/Microsoft.TestPlatform.AdapterUtilities/TestIdProvider.cs
new file mode 100644
index 0000000000..5decc3eaf0
--- /dev/null
+++ b/src/Microsoft.TestPlatform.AdapterUtilities/TestIdProvider.cs
@@ -0,0 +1,382 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.TestPlatform.AdapterUtilities
+{
+ using System;
+ using System.Text;
+
+ public class TestIdProvider
+ {
+ internal const int BlockBits = 512;
+ internal const int DigestBits = 160;
+ internal const int BlockBytes = BlockBits / 8;
+ internal const int DigestBytes = DigestBits / 8;
+
+ private Guid id = Guid.Empty;
+ private byte[] hash = null;
+ private byte[] lastBlock = new byte[BlockBytes];
+ private int position = 0;
+
+ private readonly Sha1Implementation hasher;
+
+ public TestIdProvider()
+ {
+ hasher = new Sha1Implementation();
+ }
+
+ public void AppendString(string str)
+ {
+ if (hash != null)
+ {
+ throw new InvalidOperationException();
+ }
+
+ var bytes = Encoding.Unicode.GetBytes(str);
+ var end = Math.Min(BlockBytes - position, bytes.Length);
+
+ Buffer.BlockCopy(bytes, 0, lastBlock, position, end);
+
+ // Block length is not reached yet.
+ if (end + position < BlockBytes)
+ {
+ position += end;
+ return;
+ }
+
+ hasher.ProcessBlock(lastBlock, 0, lastBlock.Length);
+ position = 0;
+
+ // We proccessed the entire string already
+ if (end == bytes.Length)
+ {
+ return;
+ }
+
+ int start = 0;
+ while (end < bytes.Length)
+ {
+ start = end;
+ end += BlockBytes;
+ if (end > bytes.Length)
+ {
+ break;
+ }
+
+ hasher.ProcessBlock(bytes, start, end - start);
+ }
+
+ if (end > bytes.Length)
+ {
+ position = bytes.Length - start;
+ Buffer.BlockCopy(bytes, start, lastBlock, 0, position);
+ }
+ }
+
+ public byte[] GetHash()
+ {
+ if (hash != null)
+ {
+ return hash;
+ }
+
+ if (position != 0)
+ {
+ hasher.PadMessage(ref lastBlock, position);
+ hasher.ProcessBlock(lastBlock, 0, lastBlock.Length);
+ }
+
+ hash = hasher.ProcessFinalBlock();
+
+ return hash;
+ }
+
+ public Guid GetId()
+ {
+ if (id != Guid.Empty)
+ {
+ return id;
+ }
+
+ var toGuid = new byte[16];
+ Array.Copy(GetHash(), toGuid, 16);
+ id = new Guid(toGuid);
+
+ return id;
+ }
+
+ ///
+ /// SHA-1 Implementation as in https://tools.ietf.org/html/rfc3174
+ ///
+ ///
+ /// This implementation only works with messages with a length
+ /// that is a multiple of the size of 8-bits.
+ ///
+ internal class Sha1Implementation
+ {
+ /*
+ * Many of the variable, function and parameter names in this code
+ * were used because those were the names used in the publication.
+ *
+ * For more information please refer to https://tools.ietf.org/html/rfc3174.
+ */
+
+ private int streamSize = 0;
+ private bool messagePadded = false;
+
+ public Sha1Implementation()
+ {
+ Reset();
+ }
+
+ ///
+ /// A sequence of logical functions to be used in SHA-1.
+ /// Each f(t), 0 <= t <= 79, operates on three 32-bit words B, C, D and produces a 32-bit word as output.
+ ///
+ /// Function index. 0 <= t <= 79
+ /// Word B
+ /// Word C
+ /// Word D
+ ///
+ /// f(t;B,C,D) = (B AND C) OR ((NOT B) AND D) ( 0 <= t <= 19)
+ /// f(t;B,C,D) = B XOR C XOR D (20 <= t <= 39)
+ /// f(t;B,C,D) = (B AND C) OR (B AND D) OR (C AND D) (40 <= t <= 59)
+ /// f(t;B,C,D) = B XOR C XOR D (60 <= t <= 79)
+ ///
+ private static uint F(int t, uint B, uint C, uint D)
+ {
+ if (t >= 0 && t <= 19)
+ {
+ return (B & C) | (~B & D);
+ }
+ else if ((t >= 20 && t <= 39) || (t >= 60 && t <= 79))
+ {
+ return B ^ C ^ D;
+ }
+ else if (t >= 40 && t <= 59)
+ {
+ return (B & C) | (B & D) | (C & D);
+ }
+ else
+ {
+ throw new ArgumentException("Argument out of bounds! 0 <= t < 80", nameof(t));
+ }
+ }
+
+ ///
+ /// Returns a constant word K(t) which is used in the SHA-1.
+ ///
+ /// Word index.
+ ///
+ /// K(t) = 0x5A827999 ( 0 <= t <= 19)
+ /// K(t) = 0x6ED9EBA1 (20 <= t <= 39)
+ /// K(t) = 0x8F1BBCDC (40 <= t <= 59)
+ /// K(t) = 0xCA62C1D6 (60 <= t <= 79)
+ ///
+ private static uint K(int t)
+ {
+ if (t >= 0 && t <= 19)
+ {
+ return 0x5A827999u;
+ }
+ else if (t >= 20 && t <= 39)
+ {
+ return 0x6ED9EBA1u;
+ }
+ else if (t >= 40 && t <= 59)
+ {
+ return 0x8F1BBCDCu;
+ }
+ else if (t >= 60 && t <= 79)
+ {
+ return 0xCA62C1D6u;
+ }
+ else
+ {
+ throw new ArgumentException("Argument out of bounds! 0 <= t < 80", nameof(t));
+ }
+ }
+
+ ///
+ /// The circular left shift operation.
+ ///
+ /// An uint word.
+ /// 0 <= n < 32
+ /// S^n(X) = (X << n) OR (X >> 32-n)
+ private static uint S(uint X, byte n)
+ {
+ if (n > 32)
+ {
+ throw new ArgumentOutOfRangeException(nameof(n));
+ }
+
+ return (X << n) | (X >> (32 - n));
+ }
+
+ ///
+ /// Ensures that given bytes are in big endian notation.
+ ///
+ /// An array of bytes
+ private static void EnsureBigEndian(ref byte[] array)
+ {
+ if (BitConverter.IsLittleEndian)
+ {
+ Array.Reverse(array);
+ }
+ }
+
+ private readonly uint[] H = new uint[5];
+
+ private void Reset()
+ {
+ streamSize = 0;
+ messagePadded = false;
+
+ // as defined in https://tools.ietf.org/html/rfc3174#section-6.1
+ H[0] = 0x67452301u;
+ H[1] = 0xEFCDAB89u;
+ H[2] = 0x98BADCFEu;
+ H[3] = 0x10325476u;
+ H[4] = 0xC3D2E1F0u;
+ }
+
+ public byte[] ComputeHash(byte[] message)
+ {
+ Reset();
+ streamSize = 0;
+ PadMessage(ref message);
+
+ ProcessBlock(message, 0, message.Length);
+
+ return ProcessFinalBlock();
+ }
+
+ private void ProcessMultipleBlocks(byte[] message)
+ {
+ var messageCount = message.Length / BlockBytes;
+ for (var i = 0; i < messageCount; i += 1)
+ {
+ ProcessBlock(message, i * BlockBytes, BlockBytes);
+ }
+ }
+
+ public byte[] ProcessFinalBlock()
+ {
+ if (!messagePadded)
+ {
+ var pad = new byte[0];
+ PadMessage(ref pad, 0);
+ ProcessBlock(pad, 0, pad.Length);
+ }
+
+ var digest = new byte[DigestBytes];
+ for (int t = 0; t < H.Length; t++)
+ {
+ var hi = BitConverter.GetBytes(H[t]);
+ EnsureBigEndian(ref hi);
+
+ Buffer.BlockCopy(hi, 0, digest, t * hi.Length, hi.Length);
+ }
+
+ return digest;
+ }
+
+ public void PadMessage(ref byte[] message, int length = 0)
+ {
+ if (messagePadded)
+ {
+ throw new InvalidOperationException();
+ }
+
+ if (length == 0)
+ {
+ length = message.Length;
+ }
+ else
+ {
+ Array.Resize(ref message, length);
+ }
+
+ streamSize += length;
+
+ var paddingBytes = BlockBytes - (length % BlockBytes);
+
+ // 64bit uint message size will be appended to end of the padding, making sure we have space for it.
+ if (paddingBytes <= 8)
+ paddingBytes += BlockBytes;
+
+ var padding = new byte[paddingBytes];
+ padding[0] = 0b10000000;
+
+ var messageBits = (ulong)streamSize << 3;
+ var messageSize = BitConverter.GetBytes(messageBits);
+ EnsureBigEndian(ref messageSize);
+
+ Buffer.BlockCopy(messageSize, 0, padding, padding.Length - messageSize.Length, messageSize.Length);
+
+ Array.Resize(ref message, message.Length + padding.Length);
+ Buffer.BlockCopy(padding, 0, message, length, padding.Length);
+
+ messagePadded = true;
+ }
+
+ public void ProcessBlock(byte[] message, int start, int length)
+ {
+ if (start + length > message.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length));
+ }
+ if (length % BlockBytes != 0)
+ {
+ throw new ArgumentException($"Invalid block size. Actual: {length}, Expected: Multiples of {BlockBytes}", nameof(length));
+ }
+ if (length != BlockBytes)
+ {
+ ProcessMultipleBlocks(message);
+ return;
+ }
+
+ streamSize += BlockBytes;
+ var W = new uint[80];
+
+ // Get W(0) .. W(15)
+ for (int t = 0; t <= 15; t++)
+ {
+ var wordBytes = new byte[sizeof(uint)];
+ Buffer.BlockCopy(message, start + (t * sizeof(uint)), wordBytes, 0, sizeof(uint));
+ EnsureBigEndian(ref wordBytes);
+
+ W[t] = BitConverter.ToUInt32(wordBytes, 0);
+ }
+
+ // Calculate W(16) .. W(79)
+ for (int t = 16; t <= 79; t++)
+ {
+ W[t] = S(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1);
+ }
+
+ uint A = H[0],
+ B = H[1],
+ C = H[2],
+ D = H[3],
+ E = H[4];
+
+ for (int t = 0; t < 80; t++)
+ {
+ var temp = S(A, 5) + F(t, B, C, D) + E + W[t] + K(t);
+ E = D;
+ D = C;
+ C = S(B, 30);
+ B = A;
+ A = temp;
+ }
+
+ H[0] += A;
+ H[1] += B;
+ H[2] += C;
+ H[3] += D;
+ H[4] += E;
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/TestIdProvider/CompatibilityTests.cs b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/TestIdProvider/CompatibilityTests.cs
new file mode 100644
index 0000000000..06220dc920
--- /dev/null
+++ b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/TestIdProvider/CompatibilityTests.cs
@@ -0,0 +1,139 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.TestPlatform.AdapterUtilities.UnitTests.TestIdProvider
+{
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using System;
+ using System.Linq;
+
+ [TestClass]
+ public class CompatibilityTests
+ {
+ [TestMethod]
+ [DataRow(new[] { "eea339da-6b5e-0d4b-3255-bfef95601890", "" })]
+ [DataRow(new[] { "740b9afc-3350-4257-ca01-5bd47799147d", "adapter://", "name1" })] // less than one block
+ [DataRow(new[] { "119c5b31-c0fb-1c12-6d1a-d617bb2bd996", "adapter://namesamplenam.testname" })] // 1 full block
+ [DataRow(new[] { "2a4c33ec-6115-4bd7-2e94-71f2fd3a5ee3", "adapter://namesamplenamespace.testname" })] // 1 full block and extra
+ [DataRow(new[] { "119c5b31-c0fb-1c12-6d1a-d617bb2bd996", "adapter://", "name", "samplenam", ".", "testname" })] // 1 full block
+ [DataRow(new[] { "2a4c33ec-6115-4bd7-2e94-71f2fd3a5ee3", "adapter://", "name", "samplenamespace", ".", "testname" })] // 1 full block and extra
+ [DataRow(new[] { "1fc07043-3d2d-1401-c732-3b507feec548", "adapter://namesamplenam.testnameaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" })] // 2 full blocks
+ [DataRow(new[] { "24e8a50b-2766-6a12-f461-9f8e4fa1cbb5", "adapter://namesamplenamespace.testnameaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" })] // 2 full blocks and extra
+ [DataRow(new[] { "1fc07043-3d2d-1401-c732-3b507feec548", "adapter://", "name", "samplenam", ".", "testname", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" })] // 2 full blocks
+ [DataRow(new[] { "24e8a50b-2766-6a12-f461-9f8e4fa1cbb5", "adapter://", "name", "samplenamespace", ".", "testname", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" })] // 2 full blocks and extra
+ public void IdCompatibilityTests(string[] data)
+ {
+ // Arrange
+ var expectedId = new Guid(data[0]);
+
+ // Act
+ var idProvider = new AdapterUtilities.TestIdProvider();
+ foreach (var d in data.Skip(1))
+ {
+ idProvider.AppendString(d);
+ }
+ var id = idProvider.GetId();
+
+ // Assert
+ Assert.AreEqual(expectedId, id);
+ }
+
+
+ [TestMethod]
+ public void IdGeneration_TestVectors_EmptyString()
+ {
+ IdGeneration_TestVector(
+ string.Empty,
+ "eea339da-6b5e-0d4b-3255-bfef95601890"
+ );
+ }
+
+
+ [TestMethod]
+ public void IdGeneration_TestVectors_abc()
+ {
+ IdGeneration_TestVector(
+ "abc",
+ "1af4049f-8584-1614-2050-e3d68c1a7abb"
+ );
+ }
+
+ [TestMethod]
+ public void IdGeneration_TestVectors_448Bits()
+ {
+ IdGeneration_TestVector(
+ "abcdbcdecdefdefgefghfghighij",
+ "7610f6db-8808-4bb7-b076-96871a96329c"
+ );
+ }
+
+ [TestMethod]
+ public void IdGeneration_TestVectors_896Bits()
+ {
+ IdGeneration_TestVector(
+ "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ "76d8d751-c79a-402c-9c5b-0e3f69c60adc"
+ );
+ }
+
+ [TestMethod]
+ public void IdGeneration_TestVectors_1Block()
+ {
+ IdGeneration_TestRepetitionVector(
+ "a", 512 / 16,
+ "99b1aec7-ff50-5229-a378-70ca37914c90"
+ );
+ }
+
+ [TestMethod]
+ public void IdGeneration_ExtremelyLarge_TestVectors_100k_abc()
+ {
+ IdGeneration_TestRepetitionVector(
+ "abc", 100_000,
+ "11dbfc20-b34a-eef6-158e-ea8c201dfff9"
+ );
+ }
+
+ [TestMethod]
+ public void IdGeneration_ExtremelyLarge_TestVectors_10M_abc()
+ {
+ IdGeneration_TestRepetitionVector(
+ "abc", 10_000_000,
+ "78640f07-8041-71bd-6461-3a7e4db52389"
+ );
+ }
+
+ private void IdGeneration_TestVector(string testName, string expected)
+ {
+ // Arrange
+ expected = expected.Replace(" ", "").ToLowerInvariant();
+ var idProvider = new AdapterUtilities.TestIdProvider();
+
+ // Act
+ idProvider.AppendString(testName);
+ var actual = idProvider.GetId().ToString();
+
+ // Assert
+ Assert.AreEqual(expected, actual, $"Test Id for '{testName}' is invalid!");
+ }
+
+ private void IdGeneration_TestRepetitionVector(string input, int repetition, string expected)
+ {
+ // Arrange
+ var idProvider = new AdapterUtilities.TestIdProvider();
+
+ // Act
+ for (int i = 0; i < repetition; i++)
+ {
+ idProvider.AppendString(input);
+ }
+
+ var id = idProvider.GetId().ToString();
+
+ // Assert
+ Assert.AreEqual(expected, id, $"Test id generation for vector '{input}'*{repetition} failed! (normal path)");
+ }
+
+ }
+}
diff --git a/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/TestIdProvider/SHA1ImplTests.cs b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/TestIdProvider/SHA1ImplTests.cs
new file mode 100644
index 0000000000..23bb10bef8
--- /dev/null
+++ b/test/Microsoft.TestPlatform.AdapterUtilities.UnitTests/TestIdProvider/SHA1ImplTests.cs
@@ -0,0 +1,173 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace Microsoft.TestPlatform.AdapterUtilities.UnitTests.TestIdProvider
+{
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+ using System;
+ using System.Linq;
+ using System.Text;
+
+ [TestClass]
+ public class SHA1ImplTests
+ {
+ [TestMethod]
+ public void SHA1_TestVectors_EmptyString()
+ {
+ SHA1_TestVector(
+ string.Empty,
+ "da39a3ee5e6b4b0d3255bfef95601890afd80709"
+ );
+ }
+
+ [TestMethod]
+ public void SHA1_TestVectors_abc()
+ {
+ SHA1_TestVector(
+ "abc",
+ "a9993e364706816aba3e25717850c26c9cd0d89d"
+ );
+ }
+
+ [TestMethod]
+ public void SHA1_TestVectors_448Bits()
+ {
+ SHA1_TestVector(
+ "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+ "84983e441c3bd26ebaae4aa1f95129e5e54670f1"
+ );
+ }
+
+ [TestMethod]
+ public void SHA1_TestVectors_896Bits()
+ {
+ SHA1_TestVector(
+ "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+ "a49b2446a02c645bf419f995b67091253a04a259"
+ );
+ }
+
+ [TestMethod]
+ public void SHA1_TestVectors_1Block()
+ {
+ SHA1_TestRepetitionVector(
+ 'a',
+ 512 / 8
+ );
+ }
+
+ [TestMethod]
+ public void SHA1_ExtremelyLarge_TestVectors_500k_a()
+ {
+ SHA1_TestRepetitionVector(
+ 'a',
+ 500_000
+ );
+ }
+
+ [TestMethod]
+ public void SHA1_ExtremelyLarge_TestVectors_900k_a()
+ {
+ SHA1_TestRepetitionVector(
+ 'a',
+ 900_000
+ );
+ }
+
+ [TestMethod]
+ public void SHA1_ExtremelyLarge_TestVectors_999999_a()
+ {
+ SHA1_TestRepetitionVector(
+ 'a',
+ 999_999
+ );
+ }
+
+ [TestMethod]
+ public void SHA1_ExtremelyLarge_TestVectors_1M_a()
+ {
+ SHA1_TestRepetitionVector(
+ 'a',
+ 1_000_000,
+ "34aa973c d4c4daa4 f61eeb2b dbad2731 6534016f"
+ );
+ }
+
+ [TestMethod]
+ public void SHA1_ExtremelyLarge_TestVectors_10M_a()
+ {
+ SHA1_TestRepetitionVector(
+ 'a',
+ 10_000_000
+ );
+ }
+
+ private void SHA1_TestVector(string message, string expected)
+ {
+ // Arrange
+ expected = expected.Replace(" ", "").ToLowerInvariant();
+ var shaHasher1 = new AdapterUtilities.TestIdProvider.Sha1Implementation();
+
+ // Act
+ var bytes = UTF8Encoding.UTF8.GetBytes(message);
+ var digest1 = ToHex(shaHasher1.ComputeHash(bytes));
+
+ // Assert
+ Assert.AreEqual(expected, digest1, $"Test vector '{message}' failed!");
+ }
+
+ private void SHA1_TestRepetitionVector(char input, int repetition, string expected = null)
+ {
+ // Arrange
+ var shaHasher1 = new AdapterUtilities.TestIdProvider.Sha1Implementation();
+ var shaHasher2 = new AdapterUtilities.TestIdProvider.Sha1Implementation();
+
+ var bytes = new byte[repetition];
+ for (int i = 0; i < repetition; i++)
+ {
+ bytes[i] = (byte)input;
+ }
+
+ if (string.IsNullOrEmpty(expected))
+ {
+ using (var hasher = System.Security.Cryptography.SHA1.Create())
+ {
+ expected = ToHex(hasher.ComputeHash(bytes));
+ }
+ }
+ else
+ {
+ expected = expected.Replace(" ", "").ToLowerInvariant();
+ }
+
+ // Act
+ var digest1 = ToHex(shaHasher1.ComputeHash(bytes));
+ var blocks = bytes.Length / AdapterUtilities.TestIdProvider.BlockBytes;
+ byte[] block;
+ for (var i = 0; i < blocks; i += 1)
+ {
+ block = new byte[AdapterUtilities.TestIdProvider.BlockBytes];
+ Buffer.BlockCopy(bytes, i * block.Length, block, 0, block.Length);
+ shaHasher2.ProcessBlock(block, 0, block.Length);
+ }
+
+ var rest = bytes.Length - blocks * AdapterUtilities.TestIdProvider.BlockBytes;
+ if (rest != 0)
+ {
+ block = new byte[rest];
+ Buffer.BlockCopy(bytes, blocks * block.Length, block, 0, block.Length);
+ shaHasher2.PadMessage(ref block, block.Length);
+ shaHasher2.ProcessBlock(block, 0, block.Length);
+ }
+
+ var digest2 = ToHex(shaHasher2.ProcessFinalBlock());
+
+ // Assert
+ Assert.AreEqual(expected, digest1, $"Test vector '{input}'*{repetition} failed! (normal path)");
+ Assert.AreEqual(expected, digest2, $"Test vector '{input}'*{repetition} failed! (padding path)");
+ }
+
+ private static string ToHex(byte[] digest) => string.Concat(digest.Select(i => i.ToString("x2")));
+ }
+}