File: Logging\SuppressionEngineTests.cs
Web Access
Project: ..\..\..\test\Microsoft.DotNet.ApiCompatibility.Tests\Microsoft.DotNet.ApiCompatibility.Tests.csproj (Microsoft.DotNet.ApiCompatibility.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable disable
 
using System.Xml.Serialization;
 
namespace Microsoft.DotNet.ApiCompatibility.Logging.Tests
{
    public class SuppressionEngineTests
    {
        [Fact]
        public void SuppressionEngine_AddSuppression_AddingTwiceDoesntThrow()
        {
            SuppressionEngine suppressionEngine = new();
            Suppression suppression = new("PKG004", "A.B()", "ref/net6.0/mylib.dll", "lib/net6.0/mylib.dll");
 
            suppressionEngine.AddSuppression(suppression);
            suppressionEngine.AddSuppression(suppression);
 
            Assert.Single(suppressionEngine.Suppressions);
        }
 
        [Fact]
        public void SuppressionEngine_IsErrorSuppressed_CanParseInputSuppressionFile()
        {
            TestSuppressionEngine suppressionEngine = new();
            suppressionEngine.LoadSuppressions("NonExistentFile.xml");
 
            // Parsed the right amount of suppressions
            Assert.Equal(9, suppressionEngine.BaselineSuppressions.Count);
 
            Assert.True(suppressionEngine.IsErrorSuppressed(new Suppression("CP0001", "T:A.B", "ref/netstandard2.0/tempValidation.dll", "lib/net6.0/tempValidation.dll")));
            Assert.False(suppressionEngine.IsErrorSuppressed(new Suppression("CP0001", "T:A.C", "ref/netstandard2.0/tempValidation.dll", "lib/net6.0/tempValidation.dll")));
            Assert.False(suppressionEngine.IsErrorSuppressed(new Suppression("CP0001", "T:A.B", "lib/netstandard2.0/tempValidation.dll", "lib/net6.0/tempValidation.dll")));
            Assert.True(suppressionEngine.IsErrorSuppressed(new Suppression("PKV004", ".netframework,Version=v4.8")));
            Assert.False(suppressionEngine.IsErrorSuppressed(new Suppression(string.Empty, string.Empty)));
            Assert.False(suppressionEngine.IsErrorSuppressed(new Suppression("PKV004", ".netframework,Version=v4.8", "lib/net6.0/mylib.dll")));
            Assert.False(suppressionEngine.IsErrorSuppressed(new Suppression("PKV004", ".NETStandard,Version=v2.0")));
            Assert.True(suppressionEngine.IsErrorSuppressed(new Suppression("CP123", "T:myValidation.Class1", isBaselineSuppression: true)));
            Assert.False(suppressionEngine.IsErrorSuppressed(new Suppression("CP123", "T:myValidation.Class1", isBaselineSuppression: false)));
            Assert.True(suppressionEngine.IsErrorSuppressed(new Suppression("CP0001", "T:A.B", "ref/netstandard2.0/tempValidation.dll", "lib/net6.0/tempValidation.dll")));
        }
 
        [Fact]
        public void SuppressionEngine_LoadFiles_ThrowsIfFileDoesNotExist()
        {
            SuppressionEngine suppressionEngine = new();
            Assert.Throws<FileNotFoundException>(() => suppressionEngine.LoadSuppressions("AFileThatDoesNotExist.xml"));
        }
 
        [Fact]
        public void SuppressionEngine_WriteSuppressionsToFile_SuppressionsRoundTrip()
        {
            string output = string.Empty;
            TestSuppressionEngine suppressionEngine = new((stream) =>
            {
                stream.Position = 0;
                using StreamReader reader = new(stream);
                output = reader.ReadToEnd();
            });
            suppressionEngine.LoadSuppressions("NonExistentFile.xml");
 
            string filePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName(), "DummyFile.xml");
            var (suppressionFileUpdated, writtenSuppressions) = suppressionEngine.WriteSuppressionsToFile(filePath, preserveUnnecessarySuppressions: true);
 
            Assert.True(suppressionFileUpdated);
            Assert.NotEmpty(writtenSuppressions);
 
            // Trimming away the comment as the serializer doesn't preserve them.
            string expectedSuppressionFile = TestSuppressionEngine.DefaultSuppressionFile.Replace(TestSuppressionEngine.SuppressionFileComment, string.Empty);
            Assert.Equal(expectedSuppressionFile.Trim(), output.Trim(), ignoreCase: true);
        }
 
        [Fact]
        public void SuppressionEngine_GetUnnecessarySuppressions_NotEmptyWithUnnecessarySuppressions()
        {
            TestSuppressionEngine suppressionEngine = new();
            suppressionEngine.LoadSuppressions("NonExistentFile.xml");
 
            IReadOnlyCollection<Suppression> unnecessarySuppressions = suppressionEngine.GetUnnecessarySuppressions();
 
            Assert.NotEmpty(unnecessarySuppressions);
        }
 
        [Fact]
        public void SuppressionEngine_BaselineSuppressions_NotEmptyWithUnnecessarySuppressions()
        {
            TestSuppressionEngine suppressionEngine = new();
            suppressionEngine.LoadSuppressions("NonExistentFile.xml");
 
            Assert.Empty(suppressionEngine.Suppressions);
            Assert.NotEmpty(suppressionEngine.BaselineSuppressions);
        }
 
        [Fact]
        public void SuppressionEngine_WriteSuppressionsToFile_ReturnsEmptyWithAllUnnecessarySuppressions()
        {
            TestSuppressionEngine suppressionEngine = new();
            suppressionEngine.LoadSuppressions("NonExistentFile.xml");
 
            string filePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName(), "DummyFile.xml");
 
            var (suppressionFileUpdated, updatedSuppressions) = suppressionEngine.WriteSuppressionsToFile(filePath);
 
            Assert.True(suppressionFileUpdated);
            Assert.Empty(updatedSuppressions);
        }
 
        [Fact]
        public void SuppressionEngine_WriteSuppressionsToFile_UnnecessarySuppressionsOmitted()
        {
            Suppression usedSuppression = new("CP0001", "T:A", "lib/netstandard1.3/tempValidation.dll", "lib/netstandard1.3/tempValidation.dll");
 
            string suppressionFile = TestSuppressionEngine.SuppressionFileHeader + @$"
  <Suppression>
    <DiagnosticId>{usedSuppression.DiagnosticId}</DiagnosticId>
    <Target>{usedSuppression.Target}</Target>
    <Left>{usedSuppression.Left}</Left>
    <Right>{usedSuppression.Right}</Right>
  </Suppression>
  <Suppression>
    <DiagnosticId>CP0001</DiagnosticId>
    <Target>T:tempValidation.Class1</Target>
    <Left>lib/netstandard1.3/tempValidation.dll</Left>
    <Right>lib/netstandard1.3/tempValidation.dll</Right>
    <IsBaselineSuppression>true</IsBaselineSuppression>
  </Suppression>
" + TestSuppressionEngine.SuppressionFileFooter;
 
            TestSuppressionEngine suppressionEngine = new(suppressionFileContent: suppressionFile);
            suppressionEngine.LoadSuppressions("NonExistentFile.xml");
            suppressionEngine.AddSuppression(usedSuppression);
 
            string filePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName(), "DummyFile.xml");
            var (suppressionFileUpdated, updatedSuppressions) = suppressionEngine.WriteSuppressionsToFile(filePath);
 
            Assert.True(suppressionFileUpdated);
            Assert.Equal([usedSuppression], updatedSuppressions);
        }
 
        [Fact]
        public void SuppressionEngine_WriteSuppressionsToFile_ReturnsEmptyWithEmptyFilePath()
        {
            EmptyTestSuppressionEngine suppressionEngine = new();
            Assert.Empty(suppressionEngine.Suppressions);
 
            var (suppressionFileUpdated, updatedSuppressions) = suppressionEngine.WriteSuppressionsToFile(string.Empty, preserveUnnecessarySuppressions: true);
            Assert.False(suppressionFileUpdated);
            Assert.Empty(updatedSuppressions);
        }
 
        [Fact]
        public void SuppressionEngine_IsErrorSuppressed_SupportsGlobalSuppressions()
        {
            SuppressionEngine engine = new();
 
            // Engine has a suppression with no left and no right. This should be treated global for any left and any right.
            engine.AddSuppression(new Suppression("CP0001", "T:A.B", isBaselineSuppression: true));
            engine.AddSuppression(new Suppression("CP0001", "T:A.C"));
 
            Assert.True(engine.IsErrorSuppressed(new Suppression("CP0001", "T:A.B", "ref/net6.0/myLib.dll", "lib/net6.0/myLib.dll", isBaselineSuppression: true)));
            Assert.False(engine.IsErrorSuppressed(new Suppression("CP0001", "T:A.B", "ref/net6.0/myLib.dll", "lib/net6.0/myLib.dll", isBaselineSuppression: false)));
 
            Assert.True(engine.IsErrorSuppressed(new Suppression("CP0001", "T:A.C", "ref/net6.0/myLib.dll", "lib/net6.0/myLib.dll", isBaselineSuppression: false)));
            Assert.False(engine.IsErrorSuppressed(new Suppression("CP0001", "T:A.C", "ref/net6.0/myLib.dll", "lib/net6.0/myLib.dll", isBaselineSuppression: true)));
 
            // Engine has a suppression with no target. Should be treated globally for any target with that left and right.
            engine.AddSuppression(new Suppression("CP0003", null, "ref/net6.0/myleft.dll", "lib/net6.0/myright.dll", isBaselineSuppression: false));
 
            Assert.True(engine.IsErrorSuppressed(new Suppression("CP0003", "T:A.B", "ref/net6.0/myLeft.dll", "lib/net6.0/myRight.dll")));
            Assert.True(engine.IsErrorSuppressed(new Suppression("CP0003", "T:A.C", "ref/net6.0/myLeft.dll", "lib/net6.0/myRight.dll")));
            Assert.True(engine.IsErrorSuppressed(new Suppression("CP0003", "T:A.D", "ref/net6.0/myLeft.dll", "lib/net6.0/myRight.dll")));
            Assert.False(engine.IsErrorSuppressed(new Suppression("CP0003", "T:A.D", "ref/net6.0/myLeft.dll", "lib/net6.0/myRight.dll", isBaselineSuppression: true)));
 
            // Engine has a suppression with no diagnostic id and target. Should be treated globally for any diagnostic id and target with that left and right.
            engine.AddSuppression(new Suppression(string.Empty, null, "ref/net8.0/left.dll", "lib/net8.0/left.dll", isBaselineSuppression: false));
            engine.AddSuppression(new Suppression(string.Empty, null, "ref/net8.0/left.dll", "lib/net8.0/left.dll", isBaselineSuppression: true));
 
            Assert.True(engine.IsErrorSuppressed(new Suppression("CP0009", "T:A.B.C.D.E", "ref/net8.0/left.dll", "lib/net8.0/left.dll", isBaselineSuppression: false)));
            Assert.True(engine.IsErrorSuppressed(new Suppression("CP0009", "T:A.B.C.D.E", "ref/net8.0/left.dll", "lib/net8.0/left.dll", isBaselineSuppression: true)));
        }
 
        [Fact]
        public void SuppressionEngine_BaseliningNewErrorsDoesNotOverrideSuppressions()
        {
            using Stream stream = new MemoryStream();
            TestSuppressionEngine suppressionEngine = new((s) =>
            {
                s.Position = 0;
                s.CopyTo(stream);
                stream.Position = 0;
            });
            suppressionEngine.LoadSuppressions("NonExistentFile.xml");
 
            Assert.Equal(9, suppressionEngine.BaselineSuppressions.Count);
 
            Suppression newSuppression = new("CP0002", "F:MyNs.Class1.Field");
            suppressionEngine.AddSuppression(newSuppression);
 
            string filePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName(), "DummyFile.xml");
 
            var (suppressionFileUpdated, updatedSuppressions) = suppressionEngine.WriteSuppressionsToFile(filePath, preserveUnnecessarySuppressions: true);
 
            Assert.True(suppressionFileUpdated);
            Assert.NotEmpty(updatedSuppressions);
 
            XmlSerializer xmlSerializer = new(typeof(Suppression[]), new XmlRootAttribute("Suppressions"));
            Suppression[] deserializedSuppressions = xmlSerializer.Deserialize(stream) as Suppression[];
            Assert.Equal(10, deserializedSuppressions.Length);
 
            Assert.Equal(new Suppression("CP0001")
            {
                Target = "T:A",
                Left = "lib/netstandard1.3/tempValidation.dll",
                Right = "lib/netstandard1.3/tempValidation.dll"
            }, deserializedSuppressions[0]);
 
            Assert.Equal(newSuppression, deserializedSuppressions[4]);
        }
 
        [Fact]
        public void SuppressionEngine_IsErrorSuppressed_NoWarnIsHonored()
        {
            SuppressionEngine engine = new(noWarn: "CP0001;CP0003;CP1111");
 
            Assert.True(engine.IsErrorSuppressed(new Suppression("CP0001", "T:A.B", "ref/net6.0/myLib.dll", "lib/net6.0/myLib.dll", isBaselineSuppression: true)));
            Assert.False(engine.IsErrorSuppressed(new Suppression("CP1110", "T:A.B", "ref/net6.0/myLib.dll", "lib/net6.0/myLib.dll", isBaselineSuppression: false)));
 
            Assert.True(engine.IsErrorSuppressed(new Suppression("CP0001", "T:A.C", "ref/net6.0/myLib.dll", "lib/net6.0/myLib.dll", isBaselineSuppression: false)));
            Assert.False(engine.IsErrorSuppressed(new Suppression("CP1000", "T:A.C", "ref/net6.0/myLib.dll", "lib/net6.0/myLib.dll", isBaselineSuppression: true)));
 
            Assert.True(engine.IsErrorSuppressed(new Suppression("CP0003", "T:A.B", "ref/net6.0/myLeft.dll", "lib/net6.0/myRight.dll")));
            Assert.True(engine.IsErrorSuppressed(new Suppression("CP0003", "T:A.C", "ref/net6.0/myLeft.dll", "lib/net6.0/myRight.dll")));
            Assert.True(engine.IsErrorSuppressed(new Suppression("CP0003", "T:A.D", "ref/net6.0/myLeft.dll", "lib/net6.0/myRight.dll")));
            Assert.False(engine.IsErrorSuppressed(new Suppression("CP1232", "T:A.D", "ref/net6.0/myLeft.dll", "lib/net6.0/myRight.dll", isBaselineSuppression: true)));
        }
    }
}