File: MultiThreadableTaskCodeFixProviderTests.cs
Web Access
Project: ..\..\..\src\ThreadSafeTaskAnalyzer.Tests\ThreadSafeTaskAnalyzer.Tests.csproj (ThreadSafeTaskAnalyzer.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Xunit;
using static Microsoft.Build.TaskAuthoring.Analyzer.Tests.TestHelpers;
 
namespace Microsoft.Build.TaskAuthoring.Analyzer.Tests;
 
/// <summary>
/// Tests for <see cref="MultiThreadableTaskCodeFixProvider"/>.
/// Uses CSharpCodeFixTest for verifying code transformations.
/// Arguments are provided with nullable annotations matching .NET 8+ BCL.
/// </summary>
public class MultiThreadableTaskCodeFixProviderTests
{
    private static CSharpCodeFixTest<MultiThreadableTaskAnalyzer, MultiThreadableTaskCodeFixProvider, DefaultVerifier> CreateFixTest(
        string testCode, string fixedCode, params DiagnosticResult[] expected)
    {
        var test = new CSharpCodeFixTest<MultiThreadableTaskAnalyzer, MultiThreadableTaskCodeFixProvider, DefaultVerifier>
        {
            TestCode = testCode,
            FixedCode = fixedCode,
            ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
        };
        test.TestState.Sources.Add(("Stubs.cs", FrameworkStubs));
        test.FixedState.Sources.Add(("Stubs.cs", FrameworkStubs));
        test.ExpectedDiagnostics.AddRange(expected);
        return test;
    }
 
    /// <summary>
    /// Creates a DiagnosticResult for the given diagnostic ID.
    /// Uses the Warning-severity descriptor since code fix tests use IMultiThreadableTask.
    /// </summary>
    private static DiagnosticResult Diag(string id) => id switch
    {
        DiagnosticIds.CriticalError => new DiagnosticResult(DiagnosticDescriptors.CriticalError),
        DiagnosticIds.TaskEnvironmentRequired => new DiagnosticResult(DiagnosticDescriptors.TaskEnvironmentRequired),
        DiagnosticIds.FilePathRequiresAbsolute => new DiagnosticResult(DiagnosticDescriptors.FilePathRequiresAbsolute),
        DiagnosticIds.PotentialIssue => new DiagnosticResult(DiagnosticDescriptors.PotentialIssue),
        DiagnosticIds.TransitiveUnsafeCall => new DiagnosticResult(DiagnosticDescriptors.TransitiveUnsafeCall),
        _ => new DiagnosticResult(id, DiagnosticSeverity.Warning),
    };
 
    [Fact]
    public async Task Fix_GetEnvironmentVariable()
    {
        await CreateFixTest(
            testCode: """
                using System;
                using Microsoft.Build.Framework;
                public class MyTask : Microsoft.Build.Utilities.Task, IMultiThreadableTask
                {
                    public TaskEnvironment TaskEnvironment { get; set; }
                    public override bool Execute()
                    {
                        var val = {|#0:Environment.GetEnvironmentVariable("PATH")|};
                        return true;
                    }
                }
                """,
            fixedCode: """
                using System;
                using Microsoft.Build.Framework;
                public class MyTask : Microsoft.Build.Utilities.Task, IMultiThreadableTask
                {
                    public TaskEnvironment TaskEnvironment { get; set; }
                    public override bool Execute()
                    {
                        var val = TaskEnvironment.GetEnvironmentVariable("PATH");
                        return true;
                    }
                }
                """,
            Diag(DiagnosticIds.TaskEnvironmentRequired).WithLocation(0)
                .WithArguments("Environment.GetEnvironmentVariable(string)", "use TaskEnvironment.GetEnvironmentVariable instead")).RunAsync();
    }
 
    [Fact]
    public async Task Fix_SetEnvironmentVariable()
    {
        await CreateFixTest(
            testCode: """
                using System;
                using Microsoft.Build.Framework;
                public class MyTask : Microsoft.Build.Utilities.Task, IMultiThreadableTask
                {
                    public TaskEnvironment TaskEnvironment { get; set; }
                    public override bool Execute()
                    {
                        {|#0:Environment.SetEnvironmentVariable("KEY", "VALUE")|};
                        return true;
                    }
                }
                """,
            fixedCode: """
                using System;
                using Microsoft.Build.Framework;
                public class MyTask : Microsoft.Build.Utilities.Task, IMultiThreadableTask
                {
                    public TaskEnvironment TaskEnvironment { get; set; }
                    public override bool Execute()
                    {
                        TaskEnvironment.SetEnvironmentVariable("KEY", "VALUE");
                        return true;
                    }
                }
                """,
            Diag(DiagnosticIds.TaskEnvironmentRequired).WithLocation(0)
                .WithArguments("Environment.SetEnvironmentVariable(string, string?)", "use TaskEnvironment.SetEnvironmentVariable instead")).RunAsync();
    }
 
    [Fact]
    public async Task Fix_PathGetFullPath()
    {
        await CreateFixTest(
            testCode: """
                using System.IO;
                using Microsoft.Build.Framework;
                public class MyTask : Microsoft.Build.Utilities.Task, IMultiThreadableTask
                {
                    public TaskEnvironment TaskEnvironment { get; set; }
                    public override bool Execute()
                    {
                        var p = {|#0:Path.GetFullPath("relative")|};
                        return true;
                    }
                }
                """,
            fixedCode: """
                using System.IO;
                using Microsoft.Build.Framework;
                public class MyTask : Microsoft.Build.Utilities.Task, IMultiThreadableTask
                {
                    public TaskEnvironment TaskEnvironment { get; set; }
                    public override bool Execute()
                    {
                        var p = TaskEnvironment.GetAbsolutePath("relative");
                        return true;
                    }
                }
                """,
            Diag(DiagnosticIds.TaskEnvironmentRequired).WithLocation(0)
                .WithArguments("Path.GetFullPath(string)", "use TaskEnvironment.GetAbsolutePath instead")).RunAsync();
    }
 
    [Fact]
    public async Task Fix_EnvironmentCurrentDirectory()
    {
        await CreateFixTest(
            testCode: """
                using System;
                using Microsoft.Build.Framework;
                public class MyTask : Microsoft.Build.Utilities.Task, IMultiThreadableTask
                {
                    public TaskEnvironment TaskEnvironment { get; set; }
                    public override bool Execute()
                    {
                        var dir = {|#0:Environment.CurrentDirectory|};
                        return true;
                    }
                }
                """,
            fixedCode: """
                using System;
                using Microsoft.Build.Framework;
                public class MyTask : Microsoft.Build.Utilities.Task, IMultiThreadableTask
                {
                    public TaskEnvironment TaskEnvironment { get; set; }
                    public override bool Execute()
                    {
                        var dir = TaskEnvironment.ProjectDirectory;
                        return true;
                    }
                }
                """,
            Diag(DiagnosticIds.TaskEnvironmentRequired).WithLocation(0)
                .WithArguments("Environment.CurrentDirectory", "use TaskEnvironment.ProjectDirectory instead")).RunAsync();
    }
 
    [Fact]
    public async Task Fix_FileExists_WrapsWithGetAbsolutePath()
    {
        await CreateFixTest(
            testCode: """
                using System.IO;
                using Microsoft.Build.Framework;
                public class MyTask : Microsoft.Build.Utilities.Task, IMultiThreadableTask
                {
                    public TaskEnvironment TaskEnvironment { get; set; }
                    public override bool Execute()
                    {
                        {|#0:File.Exists("foo.txt")|};
                        return true;
                    }
                }
                """,
            fixedCode: """
                using System.IO;
                using Microsoft.Build.Framework;
                public class MyTask : Microsoft.Build.Utilities.Task, IMultiThreadableTask
                {
                    public TaskEnvironment TaskEnvironment { get; set; }
                    public override bool Execute()
                    {
                        File.Exists(TaskEnvironment.GetAbsolutePath("foo.txt"));
                        return true;
                    }
                }
                """,
            Diag(DiagnosticIds.FilePathRequiresAbsolute).WithLocation(0)
                .WithArguments("File.Exists(string?)", "wrap path argument with TaskEnvironment.GetAbsolutePath()")).RunAsync();
    }
 
    [Fact]
    public async Task Fix_NewFileInfo_WrapsWithGetAbsolutePath()
    {
        await CreateFixTest(
            testCode: """
                using System.IO;
                using Microsoft.Build.Framework;
                public class MyTask : Microsoft.Build.Utilities.Task, IMultiThreadableTask
                {
                    public TaskEnvironment TaskEnvironment { get; set; }
                    public override bool Execute()
                    {
                        var fi = {|#0:new FileInfo("file.txt")|};
                        return true;
                    }
                }
                """,
            fixedCode: """
                using System.IO;
                using Microsoft.Build.Framework;
                public class MyTask : Microsoft.Build.Utilities.Task, IMultiThreadableTask
                {
                    public TaskEnvironment TaskEnvironment { get; set; }
                    public override bool Execute()
                    {
                        var fi = new FileInfo(TaskEnvironment.GetAbsolutePath("file.txt"));
                        return true;
                    }
                }
                """,
            Diag(DiagnosticIds.FilePathRequiresAbsolute).WithLocation(0)
                .WithArguments("new FileInfo(...)", "wrap path argument with TaskEnvironment.GetAbsolutePath()")).RunAsync();
    }
}