File: EventHandlers\PathConverter.cs
Web Access
Project: src\src\vstest\src\Microsoft.TestPlatform.CrossPlatEngine\Microsoft.TestPlatform.CrossPlatEngine.csproj (Microsoft.TestPlatform.CrossPlatEngine)
// 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.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;

using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client;
using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces;

namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities;
/// <summary>
/// Converts paths in received and sent objects, to make testhost seem like it run a local test,
/// while it was in fact running a test on a remote system, in a totally different path. This is for UWP which
/// does testhost deployment.
/// The modifications here rely on combination of side-effects, and actually replacing the values, because
/// we cannot modify the properties on our public objects, and add setters.
/// </summary>
internal class PathConverter : IPathConverter
{
    // The path on this computer to which we deployed the test dll and test runner
    private readonly string _deploymentPath = "";
    // The path on the remote system where test dll was originally placed, and from which we
    // copied it to this system. For vstest.console, which is on the other side of this, the names
    // are inverted, it sends us their local path, and thinks about our local path as remote.
    private readonly string _originalPath = "";

    public PathConverter(string originalPath, string deploymentPath, IFileHelper fileHelper)
    {
        ValidateArg.NotNull(originalPath, nameof(originalPath));
        ValidateArg.NotNull(deploymentPath, nameof(deploymentPath));
        ValidateArg.NotNull(fileHelper, nameof(fileHelper));

        string unquotedOriginalPath = originalPath.Trim('\"');
        string normalizedLocalPath = fileHelper.GetFullPath(unquotedOriginalPath).TrimEnd('\\').TrimEnd('/') + Path.DirectorySeparatorChar;
        _originalPath = normalizedLocalPath;

        string unquotedDeploymentPath = deploymentPath.Trim('\"');
        string normalizedDeploymentPath = fileHelper.GetFullPath(unquotedDeploymentPath).TrimEnd('\\').TrimEnd('/') + Path.DirectorySeparatorChar;
        _deploymentPath = normalizedDeploymentPath;
    }

    [return: NotNullIfNotNull("path")]
    public string? UpdatePath(string? path, PathConversionDirection updateDirection)
    {
        if (path == null)
            return null;

        string find;
        string replaceWith;
        if (updateDirection == PathConversionDirection.Receive)
        {
            // Request is incoming, the path that is local to the sender (for us that is "remote" path)
            // needs to be replaced with our path
            find = _originalPath;
            replaceWith = _deploymentPath;
        }
        else
        {
            find = _deploymentPath;
            replaceWith = _originalPath;
        }

        var result = path.Replace(find, replaceWith);
        return result;
    }

    public IEnumerable<string> UpdatePaths(IEnumerable<string> paths, PathConversionDirection updateDirection)
    {
        ValidateArg.NotNull(paths, nameof(paths));
        return paths.Select(p => UpdatePath(p, updateDirection)).ToList();
    }

    public TestCase UpdateTestCase(TestCase testCase, PathConversionDirection updateDirection)
    {
        ValidateArg.NotNull(testCase, nameof(testCase));
        testCase.CodeFilePath = UpdatePath(testCase.CodeFilePath, updateDirection);
        testCase.Source = UpdatePath(testCase.Source, updateDirection);
        return testCase;
    }

    public IEnumerable<TestCase> UpdateTestCases(IEnumerable<TestCase>? testCases, PathConversionDirection updateDirection)
    {
        ValidateArg.NotNull(testCases, nameof(testCases));
        testCases!.ToList().ForEach(tc => UpdateTestCase(tc, updateDirection));
        return testCases!;
    }

    public TestRunCompleteEventArgs UpdateTestRunCompleteEventArgs(TestRunCompleteEventArgs testRunCompleteEventArgs, PathConversionDirection updateDirection)
    {
        ValidateArg.NotNull(testRunCompleteEventArgs, nameof(testRunCompleteEventArgs));
        UpdateAttachmentSets(testRunCompleteEventArgs.AttachmentSets, updateDirection);
        return testRunCompleteEventArgs;
    }

    public TestRunChangedEventArgs UpdateTestRunChangedEventArgs(TestRunChangedEventArgs? testRunChangedArgs, PathConversionDirection updateDirection)
    {
        ValidateArg.NotNull(testRunChangedArgs, nameof(testRunChangedArgs));
        UpdateTestResults(testRunChangedArgs!.NewTestResults!, updateDirection);
        UpdateTestCases(testRunChangedArgs.ActiveTests, updateDirection);
        return testRunChangedArgs;
    }

    public Collection<AttachmentSet> UpdateAttachmentSets(Collection<AttachmentSet> attachmentSets, PathConversionDirection updateDirection)
    {
        ValidateArg.NotNull(attachmentSets, nameof(attachmentSets));
        attachmentSets.ToList().ForEach(i => UpdateAttachmentSet(i, updateDirection));
        return attachmentSets;
    }

    public ICollection<AttachmentSet> UpdateAttachmentSets(ICollection<AttachmentSet>? attachmentSets, PathConversionDirection updateDirection)
    {
        ValidateArg.NotNull(attachmentSets, nameof(attachmentSets));
        attachmentSets!.ToList().ForEach(i => UpdateAttachmentSet(i, updateDirection));
        return attachmentSets!;
    }

    private static AttachmentSet UpdateAttachmentSet(AttachmentSet attachmentSet, PathConversionDirection updateDirection)
    {
        ValidateArg.NotNull(attachmentSet, nameof(attachmentSet));
        attachmentSet.Attachments.ToList().ForEach(a => UpdateAttachment(a, updateDirection));
        return attachmentSet;
    }

    private static UriDataAttachment UpdateAttachment(UriDataAttachment attachment, PathConversionDirection _)
    {
        ValidateArg.NotNull(attachment, nameof(attachment));
        // todo: convert uri? https://github.com/microsoft/vstest/issues/3367
        return attachment;
    }

    private IEnumerable<TestResult> UpdateTestResults(IEnumerable<TestResult> testResults, PathConversionDirection updateDirection)
    {
        ValidateArg.NotNull(testResults, nameof(testResults));
        foreach (var tr in testResults)
        {
            UpdateAttachmentSets(tr.Attachments, updateDirection);
            UpdateTestCase(tr.TestCase, updateDirection);
        }
        return testResults;
    }

    public DiscoveryCriteria UpdateDiscoveryCriteria(DiscoveryCriteria discoveryCriteria, PathConversionDirection updateDirection)
    {
        ValidateArg.NotNull(discoveryCriteria, nameof(discoveryCriteria));
        discoveryCriteria.Package = UpdatePath(discoveryCriteria.Package, updateDirection);
        foreach (var adapter in discoveryCriteria.AdapterSourceMap.ToList())
        {
            var updatedPaths = UpdatePaths(adapter.Value, updateDirection);
            discoveryCriteria.AdapterSourceMap[adapter.Key] = updatedPaths;
        }
        return discoveryCriteria;
    }

    public TestRunCriteriaWithSources UpdateTestRunCriteriaWithSources(TestRunCriteriaWithSources testRunCriteriaWithSources, PathConversionDirection updateDirection)
    {
        ValidateArg.NotNull(testRunCriteriaWithSources, nameof(testRunCriteriaWithSources));
        testRunCriteriaWithSources.AdapterSourceMap.ToList().ForEach(adapter =>
            testRunCriteriaWithSources.AdapterSourceMap[adapter.Key] = UpdatePaths(adapter.Value, updateDirection)!);
        var package = UpdatePath(testRunCriteriaWithSources.Package, updateDirection)!;
        return new TestRunCriteriaWithSources(testRunCriteriaWithSources.AdapterSourceMap, package, testRunCriteriaWithSources.RunSettings, testRunCriteriaWithSources.TestExecutionContext);
    }

    public TestRunCriteriaWithTests UpdateTestRunCriteriaWithTests(TestRunCriteriaWithTests testRunCriteriaWithTests, PathConversionDirection updateDirection)
    {
        ValidateArg.NotNull(testRunCriteriaWithTests, nameof(testRunCriteriaWithTests));
        var tests = UpdateTestCases(testRunCriteriaWithTests.Tests, updateDirection);
        var package = UpdatePath(testRunCriteriaWithTests.Package, updateDirection)!;
        return new TestRunCriteriaWithTests(tests, package, testRunCriteriaWithTests.RunSettings, testRunCriteriaWithTests.TestExecutionContext);
    }
}