diff --git a/src/NuGet.Core/NuGet.Commands/RestoreCommand/Logging/RestoreCollectorLogger.cs b/src/NuGet.Core/NuGet.Commands/RestoreCommand/Logging/RestoreCollectorLogger.cs index 8afd2f82066..c8dc4a68ac2 100644 --- a/src/NuGet.Core/NuGet.Commands/RestoreCommand/Logging/RestoreCollectorLogger.cs +++ b/src/NuGet.Core/NuGet.Commands/RestoreCommand/Logging/RestoreCollectorLogger.cs @@ -16,6 +16,8 @@ public class RestoreCollectorLogger : LoggerBase, ICollectorLogger { private readonly ILogger _innerLogger; private readonly ConcurrentQueue _errors; + private readonly ConcurrentQueue _suppressedWarnings; + private readonly bool _hideWarningsAndErrors; private IEnumerable _restoreTargetGraphs; private PackageSpec _projectSpec; @@ -24,6 +26,8 @@ public class RestoreCollectorLogger : LoggerBase, ICollectorLogger public string ProjectPath => _projectSpec?.RestoreMetadata?.ProjectPath; public IEnumerable Errors => _errors.ToArray(); + internal IEnumerable SuppressedWarnings => _suppressedWarnings.ToArray(); + public WarningPropertiesCollection ProjectWarningPropertiesCollection { get; set; } @@ -90,6 +94,7 @@ public RestoreCollectorLogger(ILogger innerLogger, LogLevel verbosity, bool hide { _innerLogger = innerLogger; _errors = new ConcurrentQueue(); + _suppressedWarnings = new ConcurrentQueue(); _hideWarningsAndErrors = hideWarningsAndErrors; } @@ -221,23 +226,29 @@ protected bool DisplayMessage(IRestoreLogMessage message) /// bool indicating if the message should be suppressed. private bool IsWarningSuppressed(IRestoreLogMessage message) { + var isWarningSuppressed = false; if (message.Level == LogLevel.Warning) { // If the ProjectWarningPropertiesCollection is present then test if the warning is suppressed in // project wide no warn or package specific no warn if (ProjectWarningPropertiesCollection?.ApplyNoWarnProperties(message) == true) { - return true; + isWarningSuppressed = true; } else { // Use transitive warning properties only if the project does not suppress the warning // In transitive warning properties look at only the package specific ones as all properties are per package reference. - return TransitiveWarningPropertiesCollection?.ApplyNoWarnProperties(message) == true; + isWarningSuppressed = TransitiveWarningPropertiesCollection?.ApplyNoWarnProperties(message) == true; } } - return false; + if (isWarningSuppressed) + { + _suppressedWarnings.Enqueue(message); + } + + return isWarningSuppressed; } /// diff --git a/src/NuGet.Core/NuGet.Commands/RestoreCommand/RestoreCommand.cs b/src/NuGet.Core/NuGet.Commands/RestoreCommand/RestoreCommand.cs index 7fdc9cfc400..8f532b83c06 100644 --- a/src/NuGet.Core/NuGet.Commands/RestoreCommand/RestoreCommand.cs +++ b/src/NuGet.Core/NuGet.Commands/RestoreCommand/RestoreCommand.cs @@ -47,6 +47,7 @@ private readonly Dictionary> GenerateRestoreGraphsAsync(T var errorCodes = ConcatAsString(new HashSet(logs.Where(l => l.Level == LogLevel.Error).Select(l => l.Code))); var warningCodes = ConcatAsString(new HashSet(logs.Where(l => l.Level == LogLevel.Warning).Select(l => l.Code))); + var suppressedWarningCodes = ConcatAsString(new HashSet(_logger.SuppressedWarnings.Select(l => l.Code))); if (!string.IsNullOrEmpty(errorCodes)) { @@ -633,6 +635,11 @@ private async Task> GenerateRestoreGraphsAsync(T telemetry.TelemetryEvent[WarningCodes] = warningCodes; } + if (!string.IsNullOrEmpty(suppressedWarningCodes)) + { + telemetry.TelemetryEvent[SuppressedWarningCodes] = suppressedWarningCodes; + } + telemetry.TelemetryEvent[NewPackagesInstalledCount] = graphs.Where(g => !g.InConflict).SelectMany(g => g.Install).Distinct().Count(); telemetry.TelemetryEvent[RestoreSuccess] = _success; } diff --git a/test/NuGet.Core.Tests/NuGet.Commands.Test/CollectorLoggerTests.cs b/test/NuGet.Core.Tests/NuGet.Commands.Test/CollectorLoggerTests.cs index 9b43b1d8faa..0aa20a8a709 100644 --- a/test/NuGet.Core.Tests/NuGet.Commands.Test/CollectorLoggerTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Commands.Test/CollectorLoggerTests.cs @@ -1056,6 +1056,92 @@ public void CollectorLogger_DoesNotLogsWarningsForPackageSpecificNoWarnSetAndPro VerifyInnerLoggerCalls(innerLogger, LogLevel.Error, "Error", Times.Once()); } + [Fact] + public void CollectorLogger_NoSuppressedWarnings_SuppressedWarningsEmpty() + { + // Arrange + var noWarnSet = new HashSet { }; + var warnAsErrorSet = new HashSet { }; + var warningsNotAsErrors = new HashSet(); + var allWarningsAsErrors = false; + var innerLogger = new Mock(); + var collector = new RestoreCollectorLogger(innerLogger.Object) + { + ProjectWarningPropertiesCollection = new WarningPropertiesCollection( + new WarningProperties(warnAsErrorSet, noWarnSet, allWarningsAsErrors, warningsNotAsErrors), + null, + null) + }; + + // Act + collector.Log(new RestoreLogMessage(LogLevel.Warning, NuGetLogCode.NU1500, "Warning") { ShouldDisplay = true }); + + // Assert + Assert.Equal(0, collector.SuppressedWarnings.Count()); + } + + [Fact] + public void CollectorLogger_PackageSpecificNoWarnSet_SuppressedWarningsTracked() + { + // Arrange + var libraryId = "test_library"; + var frameworkString = "net45"; + var targetFramework = NuGetFramework.Parse(frameworkString); + var noWarnSet = new HashSet { }; + var warnAsErrorSet = new HashSet { }; + var warningsNotAsErrors = new HashSet(); + var allWarningsAsErrors = false; + var packageSpecificWarningProperties = new PackageSpecificWarningProperties(); + packageSpecificWarningProperties.Add(NuGetLogCode.NU1500, libraryId, targetFramework); + packageSpecificWarningProperties.Add(NuGetLogCode.NU1601, libraryId, targetFramework); + packageSpecificWarningProperties.Add(NuGetLogCode.NU1605, libraryId, targetFramework); + + var innerLogger = new Mock(); + var collector = new RestoreCollectorLogger(innerLogger.Object) + { + ProjectWarningPropertiesCollection = new WarningPropertiesCollection( + new WarningProperties(warnAsErrorSet, noWarnSet, allWarningsAsErrors, warningsNotAsErrors), + packageSpecificWarningProperties, + null) + }; + + // Act + collector.Log(RestoreLogMessage.CreateWarning(NuGetLogCode.NU1500, "Warning", libraryId, frameworkString)); + collector.Log(RestoreLogMessage.CreateWarning(NuGetLogCode.NU1601, "Warning", libraryId, frameworkString)); + collector.Log(RestoreLogMessage.CreateWarning(NuGetLogCode.NU1605, "Warning", libraryId, frameworkString)); + + // Assert + Assert.Equal(3, collector.SuppressedWarnings.Count()); + Assert.Contains(NuGetLogCode.NU1500, collector.SuppressedWarnings.Select(x => x.Code)); + Assert.Contains(NuGetLogCode.NU1601, collector.SuppressedWarnings.Select(x => x.Code)); + Assert.Contains(NuGetLogCode.NU1605, collector.SuppressedWarnings.Select(x => x.Code)); + } + + [Fact] + public void CollectorLogger_ProjectWideNoWarnSet_SuppressedWarningsTracked() + { + // Arrange + var noWarnSet = new HashSet { NuGetLogCode.NU1500 }; + var warnAsErrorSet = new HashSet { }; + var warningsNotAsErrors = new HashSet(); + var allWarningsAsErrors = false; + var innerLogger = new Mock(); + var collector = new RestoreCollectorLogger(innerLogger.Object) + { + ProjectWarningPropertiesCollection = new WarningPropertiesCollection( + new WarningProperties(warnAsErrorSet, noWarnSet, allWarningsAsErrors, warningsNotAsErrors), + null, + null) + }; + + // Act + collector.Log(new RestoreLogMessage(LogLevel.Warning, NuGetLogCode.NU1500, "Warning") { ShouldDisplay = true }); + + // Assert + Assert.Equal(1, collector.SuppressedWarnings.Count()); + Assert.Contains(NuGetLogCode.NU1500, collector.SuppressedWarnings.Select(x => x.Code)); + } + private void VerifyInnerLoggerCalls(Mock innerLogger, LogLevel messageLevel, string message, Times times, NuGetLogCode code = NuGetLogCode.Undefined, string filePath = null, string projectPath = null) { innerLogger.Verify(x => x.Log(It.Is(l => diff --git a/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/RestoreCommandTests.cs b/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/RestoreCommandTests.cs index 5c3cd2f24b7..b24d6c14bba 100644 --- a/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/RestoreCommandTests.cs +++ b/test/NuGet.Core.Tests/NuGet.Commands.Test/RestoreCommandTests/RestoreCommandTests.cs @@ -2930,6 +2930,43 @@ await SimpleTestPackageUtility.CreateFolderFeedV3Async( } } + [Fact] + public async Task ExecuteAsync_WithSuppressedWarning_PopulatesCorrectTelemetry() + { + // Arrange + using var pathContext = new SimpleTestPathContext(); + var projectName = "TestProject"; + var projectPath = Path.Combine(pathContext.SolutionRoot, projectName); + PackageSpec packageSpec = ProjectTestHelpers.GetPackageSpec(projectName, pathContext.SolutionRoot, "net472", "a"); + packageSpec.RestoreMetadata.ProjectWideWarningProperties.NoWarn.Add(NuGetLogCode.NU1603); + + await SimpleTestPackageUtility.CreateFolderFeedV3Async( + pathContext.PackageSource, + PackageSaveMode.Defaultv3, + new SimpleTestPackageContext("a", "1.5.0")); + + var logger = new TestLogger(); + + // Set-up telemetry service - Important to set-up the service *after* the package source creation call as that emits telemetry! + var telemetryEvents = new ConcurrentQueue(); + var _telemetryService = new Mock(MockBehavior.Loose); + _telemetryService + .Setup(x => x.EmitTelemetryEvent(It.IsAny())) + .Callback(x => telemetryEvents.Enqueue(x)); + + TelemetryActivity.NuGetTelemetryService = _telemetryService.Object; + + //Act + var request = ProjectTestHelpers.CreateRestoreRequest(pathContext, logger, packageSpec); + var restoreCommand = new RestoreCommand(request); + RestoreResult result = await restoreCommand.ExecuteAsync(); + + // Assert + var projectInformationEvent = telemetryEvents.Single(e => e.Name.Equals("ProjectRestoreInformation")); + Assert.Equal("NU1603", projectInformationEvent["SuppressedWarningCodes"]); + Assert.Null(projectInformationEvent["WarningCodes"]); + } + [Fact] public async Task ExecuteAsync_WithSinglePackage_WhenNoOping_PopulatesCorrectTelemetry() {