Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@
<Compile Include="..\..\shared\NullableAttributes.cs" Link="NullableAttributes.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.IO.Hashing" Version="9.0.8" />
</ItemGroup>

<ItemGroup>
<None Update="NullableHelpers.tt">
<LastGenOutput>NullableHelpers.cs</LastGenOutput>
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
#nullable enable
Microsoft.TestPlatform.AdapterUtilities.TestIdProvider2
Microsoft.TestPlatform.AdapterUtilities.TestIdProvider2.AppendBytes(byte[]! bytes) -> void
Microsoft.TestPlatform.AdapterUtilities.TestIdProvider2.AppendString(string! str) -> void
Microsoft.TestPlatform.AdapterUtilities.TestIdProvider2.GetHash() -> byte[]!
Microsoft.TestPlatform.AdapterUtilities.TestIdProvider2.GetId() -> System.Guid
Microsoft.TestPlatform.AdapterUtilities.TestIdProvider2.TestIdProvider2() -> void
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
namespace Microsoft.TestPlatform.AdapterUtilities;

/// <summary>
/// Used to generate id for tests.
/// Used to generate id for tests using SHA1.
/// </summary>
[Obsolete("TestIdProvider is deprecated and will soon be removed because it uses unsafe cryptographical hash SHA1 (for non-crypto purposes). Migrate to TestIdProvider2 that uses a non-cryptographical hash which is more appropriate for the task.")]
public class TestIdProvider
{
private Guid _id = Guid.Empty;
Expand Down
116 changes: 116 additions & 0 deletions src/Microsoft.TestPlatform.AdapterUtilities/TestIdProvider2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// 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;
using System.IO.Hashing;
using System.Text;

namespace Microsoft.TestPlatform.AdapterUtilities;
/// <summary>
/// Used to generate id for tests.
/// </summary>
public class TestIdProvider2
{
private Guid _id = Guid.Empty;
private byte[]? _hash;

private readonly XxHash128 _xxhash;

/// <summary>
/// Initializes a new instance of the <see cref="TestIdProvider"/> class.
/// </summary>
public TestIdProvider2()
{
_xxhash = new XxHash128();
}

/// <summary>
/// Appends a string to id generation seed.
/// </summary>
/// <param name="str">String to append to the id seed.</param>
/// <exception cref="InvalidOperationException">Thrown if <see cref="GetHash"/> or <see cref="GetId"/> is called already.</exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="str"/> is <see langword="null"/>.</exception>
public void AppendString(string str)
{
if (_hash != null)
{
throw new InvalidOperationException(Resources.Resources.ErrorCannotAppendAfterHashCalculation);
}
_ = str ?? throw new ArgumentNullException(nameof(str));

var bytes = Encoding.Unicode.GetBytes(str);

_xxhash.Append(bytes);
}

/// <summary>
/// Appends an array of bytes to id generation seed.
/// </summary>
/// <param name="bytes">Array to append to the id seed.</param>
/// <exception cref="InvalidOperationException">Thrown if <see cref="GetHash"/> or <see cref="GetId"/> is called already.</exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="bytes"/> is <see langword="null"/>.</exception>
public void AppendBytes(byte[] bytes)
{
if (_hash != null)
{
throw new InvalidOperationException(Resources.Resources.ErrorCannotAppendAfterHashCalculation);
}
_ = bytes ?? throw new ArgumentNullException(nameof(bytes));

if (bytes.Length == 0)
{
return;
}

_xxhash.Append(bytes);
}

/// <summary>
/// Calculates the Id seed.
/// </summary>
/// <returns>An array containing the seed.</returns>
/// <remarks>
/// <see cref="AppendBytes(byte[])"/> and <see cref="AppendString(string)"/> cannot be called
/// on instance after this method is called.
/// </remarks>
public byte[] GetHash()
{
if (_hash != null)
{
return _hash;
}

// Finalize the hash. We don't have any more data so we provide empty.
_hash = _xxhash.GetCurrentHash();

return _hash!;
}

/// <summary>
/// Calculates the Id from the seed.
/// </summary>
/// <returns>Id</returns>
/// <remarks>
/// <see cref="AppendBytes(byte[])"/> and <see cref="AppendString(string)"/> cannot be called
/// on instance after this method is called.
/// </remarks>
public Guid GetId()
{
if (_id != Guid.Empty)
{
return _id;
}

#if NET6_0_OR_GREATER
var hashSlice = GetHash().AsSpan().Slice(0, 16);
_id = new Guid(hashSlice);
#else
// create from span?
var toGuid = new byte[16];
Array.Copy(GetHash(), toGuid, 16);
_id = new Guid(toGuid);
#endif

return _id;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
<PackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataVersion)" Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.IO.Hashing" Version="9.0.8" />
</ItemGroup>

<ItemGroup>
<None Include="Resources\CommonResources.tt">
<Generator>TextTemplatingFileGenerator</Generator>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ Microsoft.VisualStudio.TestPlatform.ObjectModel.TelemetryEvent
Microsoft.VisualStudio.TestPlatform.ObjectModel.TelemetryEvent.Name.get -> string!
Microsoft.VisualStudio.TestPlatform.ObjectModel.TelemetryEvent.Properties.get -> System.Collections.Generic.IDictionary<string!, object!>!
Microsoft.VisualStudio.TestPlatform.ObjectModel.TelemetryEvent.TelemetryEvent(string! name, System.Collections.Generic.IDictionary<string!, object!>! properties) -> void
static Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities.EqtHash.GuidFromString2(string! data) -> System.Guid
2 changes: 1 addition & 1 deletion src/Microsoft.TestPlatform.ObjectModel/TestCase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ private Guid GetTestId()
// If ManagedType and ManagedMethod properties are filled than TestId should be based on those.
testcaseFullName += GetFullyQualifiedName();

return EqtHash.GuidFromString(testcaseFullName);
return EqtHash.GuidFromString2(testcaseFullName);
}

private void SetVariableAndResetId<T>(ref T variable, T value)
Expand Down
30 changes: 29 additions & 1 deletion src/Microsoft.TestPlatform.ObjectModel/Utilities/EqtHash.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.IO.Hashing;
using System.Security.Cryptography;

using Microsoft.VisualStudio.TestPlatform.CoreUtilities;

Expand All @@ -17,6 +19,8 @@ public static class EqtHash
/// Calculates a hash of the string and copies the first 128 bits of the hash
/// to a new Guid.
/// </summary>
[Obsolete("GuidFromString is deprecated and will soon be removed because it uses unsafe cryptographical hash SHA1 (for non-crypto purposes). Migrate to GuidFromString2 that uses a non-cryptographical hash which is more appropriate for the task.")]

public static Guid GuidFromString(string data)
{
TPDebug.Assert(data != null);
Expand All @@ -27,7 +31,8 @@ public static Guid GuidFromString(string data)
// Any algorithm or logic change must require a sign off from feature owners of above
// Also, TPV2 and TPV1 must use same Algorithm until the time TPV1 is completely deleted to be on-par
// If LUT or .Net core scenario uses TPV2 to discover, but if it uses TPV1 in Devenv, then there will be testcase matching issues
byte[] hash = Sha1Helper.ComputeSha1(System.Text.Encoding.Unicode.GetBytes(data));
using HashAlgorithm provider = SHA1.Create();
byte[] hash = provider.ComputeHash(System.Text.Encoding.Unicode.GetBytes(data));

// Guid is always 16 bytes
TPDebug.Assert(Guid.Empty.ToByteArray().Length == 16, "Expected Guid to be 16 bytes");
Expand All @@ -37,4 +42,27 @@ public static Guid GuidFromString(string data)

return new Guid(toGuid);
}

/// <summary>
/// Calculates a hash of the string and copies the first 128 bits of the hash
/// to a new Guid.
/// </summary>
public static Guid GuidFromString2(string data)
{
TPDebug.Assert(data != null);

byte[] hash = XxHash128.Hash(System.Text.Encoding.Unicode.GetBytes(data));

// Guid is always 16 bytes
TPDebug.Assert(Guid.Empty.ToByteArray().Length == 16, "Expected Guid to be 16 bytes");

#if NET6_0_OR_GREATER
return new Guid(hash.AsSpan().Slice(0, 16));
#else
// create from span?
var toGuid = new byte[16];
Array.Copy(hash, toGuid, 16);
return new Guid(toGuid);
#endif
}
}
Loading
Loading