File: src\vstest\src\Microsoft.TestPlatform.Hashing.Source\TestIdSeed.cs
Web Access
Project: Microsoft.TestPlatform.ObjectModel.csproj (Microsoft.VisualStudio.TestPlatform.ObjectModel)
// 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;

namespace Microsoft.TestPlatform.Hashing;

/// <summary>
/// Builds the string a test case id is hashed from.
/// </summary>
/// <remarks>
/// Shared source, compiled into <c>Microsoft.TestPlatform.ObjectModel</c>, where <c>TestCase</c>
/// computes its own id. <c>Microsoft.TestPlatform.CrossPlatEngine</c>, where the
/// Microsoft.Testing.Platform path has to compute the id in the runner process, uses that same
/// definition through <c>InternalsVisibleTo</c>, as does the temporary test id report logger
/// (<c>Microsoft.VisualStudio.TestPlatform.Extensions.TestIds.TestLogger</c>), which reports what
/// each algorithm computes and so has to hash exactly these bytes; that grant goes away with it.
/// All must derive the id from exactly the same bytes, so the composition lives in one place rather
/// than being written out several times and silently drifting apart.
/// </remarks>
internal static class TestIdSeed
{
    /// <summary>
    /// Composes the seed for a test case id: executor uri + source file name + fully qualified name.
    /// </summary>
    /// <param name="executorUri">The executor uri, as text.</param>
    /// <param name="source">The test container the test was found in.</param>
    /// <param name="fullyQualifiedName">
    /// The fully qualified name to use. This is the managed type and method based name when the test
    /// case carries those properties, and the plain fully qualified name otherwise; the caller
    /// resolves which, because only it knows.
    /// </param>
    /// <remarks>
    /// The parameters are nullable because this reproduces a concatenation of possibly-null values:
    /// a test case built through the serialization constructor has not had them assigned yet, and
    /// asking such an instance for its id used to yield a seed with the missing parts empty rather
    /// than throwing. Null and empty must therefore stay indistinguishable here.
    /// </remarks>
    public static string Compose(string? executorUri, string? source, string? fullyQualifiedName)
    {
        // If source is a file name then just use the filename for the identifier since the
        // file might have moved between discovery and execution (in appx mode for example)
        // This is not elegant because the Source contents should be a black box to the framework.
        // For example in the database adapter case this is not a file path.
        // As discussed with team, we found no scenario for netcore, & fullclr where the Source is not present where ID is generated,
        // which means we would always use FileName to generate ID. In cases where somehow Source Path contained garbage character the API Path.GetFileName()
        // we are simply returning original input.
        // For UWP where source during discovery, & during execution can be on different machine, in such case we should always use Path.GetFileName()
        string? fileNameOrSource = source;
        try
        {
            // If source name is malformed, GetFileName API will throw exception, so use same input malformed string to generate ID
            fileNameOrSource = Path.GetFileName(source);
        }
        catch
        {
            // do nothing
        }

        return executorUri + fileNameOrSource + fullyQualifiedName;
    }
}