| File: TestIdProvider.cs | Web Access |
| Project: src\vstest\src\Microsoft.TestPlatform.AdapterUtilities\Microsoft.TestPlatform.AdapterUtilities.csproj (Microsoft.TestPlatform.AdapterUtilities) |
// 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.Security.Cryptography; using System.Text; namespace Microsoft.TestPlatform.AdapterUtilities; /// <summary> /// Used to generate id for tests. /// </summary> public class TestIdProvider { private Guid _id = Guid.Empty; private byte[]? _hash; private readonly SHA1 _sha; /// <summary> /// Initializes a new instance of the <see cref="TestIdProvider"/> class. /// </summary> public TestIdProvider() { _sha = SHA1.Create(); } /// <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); _sha.TransformBlock(bytes, 0, bytes.Length, null, 0); } /// <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; } _sha.TransformBlock(bytes, 0, bytes.Length, null, 0); } /// <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. _sha.TransformFinalBlock(Array.Empty<byte>(), 0, 0); _hash = _sha.Hash; 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 NET 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; } }