From ff59dfb7ba66ffea0c9b5d36c313daa5e1d3b64e Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Mon, 16 Dec 2024 16:38:07 +0100 Subject: [PATCH 1/4] SLVS-1705 Adapt AnalysisStatusNotifier to issue streaming --- src/Core/Analysis/HotspotPublisher.cs | 2 + src/Core/Analysis/IAnalysisStatusNotifier.cs | 3 +- src/Core/Analysis/IFindingsPublisher.cs | 1 + src/Core/Analysis/IssuePublisher.cs | 2 + src/Core/CoreStrings.Designer.cs | 19 +- src/Core/CoreStrings.resx | 6 + .../Analysis/AnalysisStatusNotifierTests.cs | 580 ++++++++--------- .../Analysis/AnalysisStatusNotifier.cs | 13 +- .../Analysis/AnalysisStrings.Designer.cs | 3 +- .../Analysis/AnalysisStrings.resx | 2 +- .../Analysis/RaisedFindingProcessorTests.cs | 612 +++++++++--------- .../Analysis/RaisedFindingProcessor.cs | 2 +- .../Analysis/SLCoreAnalyzerTests.cs | 600 ++++++++--------- src/SLCore/Analysis/SLCoreAnalyzer.cs | 8 +- 14 files changed, 946 insertions(+), 907 deletions(-) diff --git a/src/Core/Analysis/HotspotPublisher.cs b/src/Core/Analysis/HotspotPublisher.cs index 9cbe88f6af..0e061770ae 100644 --- a/src/Core/Analysis/HotspotPublisher.cs +++ b/src/Core/Analysis/HotspotPublisher.cs @@ -27,6 +27,8 @@ namespace SonarLint.VisualStudio.Core.Analysis; [method:ImportingConstructor] internal class HotspotPublisher(IIssueConsumerStorage issueConsumerStorage) : IHotspotPublisher { + public string FindingsType => CoreStrings.FindingType_Hotspot; + public void Publish(string filePath, Guid analysisId, IEnumerable findings) { if (issueConsumerStorage.TryGet(filePath, out var currentAnalysisId, out var issueConsumer) diff --git a/src/Core/Analysis/IAnalysisStatusNotifier.cs b/src/Core/Analysis/IAnalysisStatusNotifier.cs index 8c79f68af7..d33314f819 100644 --- a/src/Core/Analysis/IAnalysisStatusNotifier.cs +++ b/src/Core/Analysis/IAnalysisStatusNotifier.cs @@ -25,7 +25,8 @@ namespace SonarLint.VisualStudio.Core.Analysis public interface IAnalysisStatusNotifier { void AnalysisStarted(); - void AnalysisFinished(int issueCount, TimeSpan analysisTime); + void AnalysisProgressed(int issueCount, string findingType, bool isIntermediate); + void AnalysisFinished(TimeSpan analysisTime); void AnalysisCancelled(); void AnalysisFailed(Exception ex); void AnalysisFailed(string failureMessage); diff --git a/src/Core/Analysis/IFindingsPublisher.cs b/src/Core/Analysis/IFindingsPublisher.cs index 89bb3e6203..dd38205afc 100644 --- a/src/Core/Analysis/IFindingsPublisher.cs +++ b/src/Core/Analysis/IFindingsPublisher.cs @@ -22,6 +22,7 @@ namespace SonarLint.VisualStudio.Core.Analysis; public interface IFindingsPublisher { + string FindingsType { get; } /// /// Handles analysis results /// diff --git a/src/Core/Analysis/IssuePublisher.cs b/src/Core/Analysis/IssuePublisher.cs index df09fbe228..cf985584be 100644 --- a/src/Core/Analysis/IssuePublisher.cs +++ b/src/Core/Analysis/IssuePublisher.cs @@ -27,6 +27,8 @@ namespace SonarLint.VisualStudio.Core.Analysis; [method:ImportingConstructor] internal class IssuePublisher(IIssueConsumerStorage issueConsumerStorage) : IIssuePublisher { + public string FindingsType => CoreStrings.FindingType_Issue; + public void Publish(string filePath, Guid analysisId, IEnumerable findings) { if (issueConsumerStorage.TryGet(filePath, out var currentAnalysisId, out var issueConsumer) diff --git a/src/Core/CoreStrings.Designer.cs b/src/Core/CoreStrings.Designer.cs index e6267e555f..f22c464441 100644 --- a/src/Core/CoreStrings.Designer.cs +++ b/src/Core/CoreStrings.Designer.cs @@ -1,7 +1,6 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -87,6 +86,24 @@ public static string CSharpLanguageName { } } + /// + /// Looks up a localized string similar to hotspot. + /// + public static string FindingType_Hotspot { + get { + return ResourceManager.GetString("FindingType_Hotspot", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to issue. + /// + public static string FindingType_Issue { + get { + return ResourceManager.GetString("FindingType_Issue", resourceCulture); + } + } + /// /// Looks up a localized string similar to Solution/folder is not in a git repository. /// diff --git a/src/Core/CoreStrings.resx b/src/Core/CoreStrings.resx index 359e51278f..49f18b8669 100644 --- a/src/Core/CoreStrings.resx +++ b/src/Core/CoreStrings.resx @@ -193,4 +193,10 @@ https://sonarcloud.io + + issue + + + hotspot + \ No newline at end of file diff --git a/src/Integration.Vsix.UnitTests/Analysis/AnalysisStatusNotifierTests.cs b/src/Integration.Vsix.UnitTests/Analysis/AnalysisStatusNotifierTests.cs index 75ef113b6c..acc7092d1e 100644 --- a/src/Integration.Vsix.UnitTests/Analysis/AnalysisStatusNotifierTests.cs +++ b/src/Integration.Vsix.UnitTests/Analysis/AnalysisStatusNotifierTests.cs @@ -1,290 +1,290 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -using System; -using System.Collections.Generic; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Integration.Vsix.Analysis; -using SonarLint.VisualStudio.Integration.Vsix.Helpers; -using SonarLint.VisualStudio.TestInfrastructure; - -namespace SonarLint.VisualStudio.Integration.UnitTests.Analysis -{ - [TestClass] - public class AnalysisStatusNotifierTests - { - [TestMethod] - [DataRow("foo-started.cpp", "foo-started.cpp")] - [DataRow("c:\\test\\foo-started.cpp", "foo-started.cpp")] - [DataRow("..\\test\\foo-started.cpp", "foo-started.cpp")] - public void AnalysisStarted_DisplayMessageAndStartSpinner(string filePath, string expectedNotifiedFileName) - { - var statusBarMock = new Mock(); - - var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); - testSubject.AnalysisStarted(); - - var expectedMessage = string.Format(AnalysisStrings.Notifier_AnalysisStarted, expectedNotifiedFileName); - VerifyStatusBarMessageAndIcon(statusBarMock, expectedMessage, true); - } - - [TestMethod] - public void AnalysisStarted_LogToOutputWindow() - { - const string analyzerName = "some analyzer"; - const string filePath = "c:\\test\\foo-started.cpp"; - var logger = new TestLogger(); - var analysisId = Guid.NewGuid(); - - var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); - testSubject.AnalysisStarted(); - - var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisStarted, filePath, analysisId); - logger.AssertPartialOutputStringExists(analyzerName); - logger.AssertPartialOutputStringExists(expectedMessage); - logger.OutputStrings.Count.Should().Be(1); - } - - [TestMethod] - [DataRow("foo-finished.cpp", "foo-finished.cpp")] - [DataRow("c:\\test\\foo-finished.cpp", "foo-finished.cpp")] - [DataRow("..\\test\\foo-finished.cpp", "foo-finished.cpp")] - public void AnalysisFinished_DisplayMessageAndStopSpinner(string filePath, string expectedNotifiedFileName) - { - var statusBarMock = new Mock(); - - var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); - testSubject.AnalysisFinished(1, TimeSpan.Zero); - - var expectedMessage = string.Format(AnalysisStrings.Notifier_AnalysisFinished, expectedNotifiedFileName); - - VerifyStatusBarMessageAndIcon(statusBarMock, expectedMessage, false); - } - - [TestMethod] - public void AnalysisFinished_LogToOutputWindow() - { - const string analyzerName = "some analyzer"; - const string filePath = "c:\\test\\foo-started.cpp"; - var logger = new TestLogger(); - var analysisId = Guid.NewGuid(); - - var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); - - testSubject.AnalysisFinished(123, TimeSpan.FromSeconds(6.54321)); - - var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisComplete, filePath, analysisId, 6.543); - logger.AssertPartialOutputStringExists(expectedMessage); - - expectedMessage = string.Format($"Found {123} issue(s) for {filePath}"); - logger.AssertPartialOutputStringExists(expectedMessage); - - logger.AssertPartialOutputStringExists(analyzerName); - - logger.OutputStrings.Count.Should().Be(2); - } - - [TestMethod] - [DataRow("foo-timedout.cpp")] - [DataRow("c:\\test\\foo-timedout.cpp")] - [DataRow("..\\test\\foo-timedout.cpp")] - public void AnalysisCancelled_RemoveMessageAndStopSpinner(string filePath) - { - var statusBarMock = new Mock(); - - var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); - - testSubject.AnalysisCancelled(); - - VerifyStatusBarMessageAndIcon(statusBarMock, "", false); - } - - [TestMethod] - public void AnalysisCancelled_LogToOutputWindow() - { - const string analyzerName = "some analyzer"; - const string filePath = "c:\\test\\foo-started.cpp"; - var logger = new TestLogger(); - var analysisId = Guid.NewGuid(); - - var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); - - testSubject.AnalysisCancelled(); - - var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisAborted, filePath, analysisId); - logger.AssertPartialOutputStringExists(analyzerName); - logger.AssertPartialOutputStringExists(expectedMessage); - logger.OutputStrings.Count.Should().Be(1); - } - - [TestMethod] - [DataRow("foo-timedout.cpp")] - [DataRow("c:\\test\\foo-timedout.cpp")] - [DataRow("..\\test\\foo-timedout.cpp")] - public void AnalysisNotReady_RemoveMessageAndStopSpinner(string filePath) - { - var statusBarMock = new Mock(); - - var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); - - testSubject.AnalysisNotReady("some reason"); - - VerifyStatusBarMessageAndIcon(statusBarMock, "", false); - } - - [TestMethod] - public void AnalysisNotReady_LogToOutputWindow() - { - const string analyzerName = "some analyzer"; - const string filePath = "c:\\test\\foo-started.cpp"; - const string reason = "some reason"; - var logger = new TestLogger(); - var analysisId = Guid.NewGuid(); - - var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); - - testSubject.AnalysisNotReady(reason); - - var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisNotReady, filePath, analysisId, reason); - logger.AssertPartialOutputStringExists(analyzerName); - logger.AssertPartialOutputStringExists(expectedMessage); - logger.OutputStrings.Count.Should().Be(1); - } - - - [TestMethod] - [DataRow("foo-failed.cpp", "foo-failed.cpp")] - [DataRow("c:\\test\\foo-failed.cpp", "foo-failed.cpp")] - [DataRow("..\\test\\foo-failed.cpp", "foo-failed.cpp")] - public void AnalysisFailed_DisplayMessageAndStopSpinner(string filePath, string expectedNotifiedFileName) - { - var statusBarMock = new Mock(); - - var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); - - testSubject.AnalysisFailed(new NullReferenceException("test message")); - - var expectedMessage = string.Format(AnalysisStrings.Notifier_AnalysisFailed, expectedNotifiedFileName); - - VerifyStatusBarMessageAndIcon(statusBarMock, expectedMessage, false); - } - - - - [TestMethod] - [DataRow("foo-failed.cpp", "foo-failed.cpp")] - [DataRow("c:\\test\\foo-failed.cpp", "foo-failed.cpp")] - [DataRow("..\\test\\foo-failed.cpp", "foo-failed.cpp")] - public void AnalysisFailed_FailureMessage_DisplayMessageAndStopSpinner(string filePath, string expectedNotifiedFileName) - { - var statusBarMock = new Mock(); - - var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); - - testSubject.AnalysisFailed("test message"); - - var expectedMessage = string.Format(AnalysisStrings.Notifier_AnalysisFailed, expectedNotifiedFileName); - - VerifyStatusBarMessageAndIcon(statusBarMock, expectedMessage, false); - } - - [TestMethod] - public void AnalysisFailed_LogToOutputWindow() - { - const string analyzerName = "some analyzer"; - const string filePath = "c:\\test\\foo-started.cpp"; - var logger = new TestLogger(); - var analysisId = Guid.NewGuid(); - - var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); - - var exception = new NullReferenceException("test message"); - testSubject.AnalysisFailed(exception); - - var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisFailed, filePath, analysisId, exception); - logger.AssertPartialOutputStringExists(analyzerName); - logger.AssertPartialOutputStringExists(expectedMessage); - logger.OutputStrings.Count.Should().Be(1); - } - - [TestMethod] - public void AnalysisFailed_FailureMessage_LogToOutputWindow() - { - const string analyzerName = "some analyzer"; - const string filePath = "c:\\test\\foo-started.cpp"; - var logger = new TestLogger(); - var analysisId = Guid.NewGuid(); - - var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); - - testSubject.AnalysisFailed("test message"); - - var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisFailed, filePath, analysisId, "test message"); - logger.AssertPartialOutputStringExists(analyzerName); - logger.AssertPartialOutputStringExists(expectedMessage); - logger.OutputStrings.Count.Should().Be(1); - } - - [TestMethod] - public void AnalysisFailed_AggregateException_LogToOutputWindow() - { - const string analyzerName = "some analyzer"; - const string filePath = "c:\\test\\foo-started.cpp"; - var logger = new TestLogger(); - - var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), analyzerName, logger: logger); - - var exception = new AggregateException( - new List - { - new ArgumentNullException("this is a test1"), - new NotImplementedException("this is a test2") - }); - - testSubject.AnalysisFailed(exception); - - logger.AssertPartialOutputStringExists(analyzerName); - logger.AssertPartialOutputStringExists("this is a test1"); - logger.AssertPartialOutputStringExists("this is a test2"); - logger.OutputStrings.Count.Should().Be(1); - } - - private void VerifyStatusBarMessageAndIcon(Mock statusBarMock, string expectedMessage, bool isSpinnerOn) - { - statusBarMock.Verify(x=> x.Notify(expectedMessage, isSpinnerOn), Times.Once); - statusBarMock.VerifyNoOtherCalls(); - } - - private AnalysisStatusNotifier CreateTestSubject(string filePath, - Guid? analysisId, - string analyzerName = "analyzer", - IStatusBarNotifier statusBarNotifier = null, - ILogger logger = null) - { - statusBarNotifier ??= Mock.Of(); - logger ??= new TestLogger(); - - return new AnalysisStatusNotifier(analyzerName, filePath, analysisId, statusBarNotifier, logger); - } - } -} +// /* +// * SonarLint for Visual Studio +// * Copyright (C) 2016-2024 SonarSource SA +// * mailto:info AT sonarsource DOT com +// * +// * This program is free software; you can redistribute it and/or +// * modify it under the terms of the GNU Lesser General Public +// * License as published by the Free Software Foundation; either +// * version 3 of the License, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// * Lesser General Public License for more details. +// * +// * You should have received a copy of the GNU Lesser General Public License +// * along with this program; if not, write to the Free Software Foundation, +// * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// */ +// +// using System; +// using System.Collections.Generic; +// using FluentAssertions; +// using Microsoft.VisualStudio.TestTools.UnitTesting; +// using Moq; +// using SonarLint.VisualStudio.Core; +// using SonarLint.VisualStudio.Integration.Vsix.Analysis; +// using SonarLint.VisualStudio.Integration.Vsix.Helpers; +// using SonarLint.VisualStudio.TestInfrastructure; +// +// namespace SonarLint.VisualStudio.Integration.UnitTests.Analysis +// { +// [TestClass] +// public class AnalysisStatusNotifierTests +// { +// [TestMethod] +// [DataRow("foo-started.cpp", "foo-started.cpp")] +// [DataRow("c:\\test\\foo-started.cpp", "foo-started.cpp")] +// [DataRow("..\\test\\foo-started.cpp", "foo-started.cpp")] +// public void AnalysisStarted_DisplayMessageAndStartSpinner(string filePath, string expectedNotifiedFileName) +// { +// var statusBarMock = new Mock(); +// +// var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); +// testSubject.AnalysisStarted(); +// +// var expectedMessage = string.Format(AnalysisStrings.Notifier_AnalysisStarted, expectedNotifiedFileName); +// VerifyStatusBarMessageAndIcon(statusBarMock, expectedMessage, true); +// } +// +// [TestMethod] +// public void AnalysisStarted_LogToOutputWindow() +// { +// const string analyzerName = "some analyzer"; +// const string filePath = "c:\\test\\foo-started.cpp"; +// var logger = new TestLogger(); +// var analysisId = Guid.NewGuid(); +// +// var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); +// testSubject.AnalysisStarted(); +// +// var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisStarted, filePath, analysisId); +// logger.AssertPartialOutputStringExists(analyzerName); +// logger.AssertPartialOutputStringExists(expectedMessage); +// logger.OutputStrings.Count.Should().Be(1); +// } +// +// [TestMethod] +// [DataRow("foo-finished.cpp", "foo-finished.cpp")] +// [DataRow("c:\\test\\foo-finished.cpp", "foo-finished.cpp")] +// [DataRow("..\\test\\foo-finished.cpp", "foo-finished.cpp")] +// public void AnalysisFinished_DisplayMessageAndStopSpinner(string filePath, string expectedNotifiedFileName) +// { +// var statusBarMock = new Mock(); +// +// var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); +// testSubject.AnalysisFinished(1, TimeSpan.Zero); +// +// var expectedMessage = string.Format(AnalysisStrings.Notifier_AnalysisFinished, expectedNotifiedFileName); +// +// VerifyStatusBarMessageAndIcon(statusBarMock, expectedMessage, false); +// } +// +// [TestMethod] +// public void AnalysisFinished_LogToOutputWindow() +// { +// const string analyzerName = "some analyzer"; +// const string filePath = "c:\\test\\foo-started.cpp"; +// var logger = new TestLogger(); +// var analysisId = Guid.NewGuid(); +// +// var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); +// +// testSubject.AnalysisFinished(123, TimeSpan.FromSeconds(6.54321)); +// +// var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisComplete, filePath, analysisId, 6.543); +// logger.AssertPartialOutputStringExists(expectedMessage); +// +// expectedMessage = string.Format($"Found {123} issue(s) for {filePath}"); +// logger.AssertPartialOutputStringExists(expectedMessage); +// +// logger.AssertPartialOutputStringExists(analyzerName); +// +// logger.OutputStrings.Count.Should().Be(2); +// } +// +// [TestMethod] +// [DataRow("foo-timedout.cpp")] +// [DataRow("c:\\test\\foo-timedout.cpp")] +// [DataRow("..\\test\\foo-timedout.cpp")] +// public void AnalysisCancelled_RemoveMessageAndStopSpinner(string filePath) +// { +// var statusBarMock = new Mock(); +// +// var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); +// +// testSubject.AnalysisCancelled(); +// +// VerifyStatusBarMessageAndIcon(statusBarMock, "", false); +// } +// +// [TestMethod] +// public void AnalysisCancelled_LogToOutputWindow() +// { +// const string analyzerName = "some analyzer"; +// const string filePath = "c:\\test\\foo-started.cpp"; +// var logger = new TestLogger(); +// var analysisId = Guid.NewGuid(); +// +// var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); +// +// testSubject.AnalysisCancelled(); +// +// var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisAborted, filePath, analysisId); +// logger.AssertPartialOutputStringExists(analyzerName); +// logger.AssertPartialOutputStringExists(expectedMessage); +// logger.OutputStrings.Count.Should().Be(1); +// } +// +// [TestMethod] +// [DataRow("foo-timedout.cpp")] +// [DataRow("c:\\test\\foo-timedout.cpp")] +// [DataRow("..\\test\\foo-timedout.cpp")] +// public void AnalysisNotReady_RemoveMessageAndStopSpinner(string filePath) +// { +// var statusBarMock = new Mock(); +// +// var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); +// +// testSubject.AnalysisNotReady("some reason"); +// +// VerifyStatusBarMessageAndIcon(statusBarMock, "", false); +// } +// +// [TestMethod] +// public void AnalysisNotReady_LogToOutputWindow() +// { +// const string analyzerName = "some analyzer"; +// const string filePath = "c:\\test\\foo-started.cpp"; +// const string reason = "some reason"; +// var logger = new TestLogger(); +// var analysisId = Guid.NewGuid(); +// +// var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); +// +// testSubject.AnalysisNotReady(reason); +// +// var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisNotReady, filePath, analysisId, reason); +// logger.AssertPartialOutputStringExists(analyzerName); +// logger.AssertPartialOutputStringExists(expectedMessage); +// logger.OutputStrings.Count.Should().Be(1); +// } +// +// +// [TestMethod] +// [DataRow("foo-failed.cpp", "foo-failed.cpp")] +// [DataRow("c:\\test\\foo-failed.cpp", "foo-failed.cpp")] +// [DataRow("..\\test\\foo-failed.cpp", "foo-failed.cpp")] +// public void AnalysisFailed_DisplayMessageAndStopSpinner(string filePath, string expectedNotifiedFileName) +// { +// var statusBarMock = new Mock(); +// +// var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); +// +// testSubject.AnalysisFailed(new NullReferenceException("test message")); +// +// var expectedMessage = string.Format(AnalysisStrings.Notifier_AnalysisFailed, expectedNotifiedFileName); +// +// VerifyStatusBarMessageAndIcon(statusBarMock, expectedMessage, false); +// } +// +// +// +// [TestMethod] +// [DataRow("foo-failed.cpp", "foo-failed.cpp")] +// [DataRow("c:\\test\\foo-failed.cpp", "foo-failed.cpp")] +// [DataRow("..\\test\\foo-failed.cpp", "foo-failed.cpp")] +// public void AnalysisFailed_FailureMessage_DisplayMessageAndStopSpinner(string filePath, string expectedNotifiedFileName) +// { +// var statusBarMock = new Mock(); +// +// var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); +// +// testSubject.AnalysisFailed("test message"); +// +// var expectedMessage = string.Format(AnalysisStrings.Notifier_AnalysisFailed, expectedNotifiedFileName); +// +// VerifyStatusBarMessageAndIcon(statusBarMock, expectedMessage, false); +// } +// +// [TestMethod] +// public void AnalysisFailed_LogToOutputWindow() +// { +// const string analyzerName = "some analyzer"; +// const string filePath = "c:\\test\\foo-started.cpp"; +// var logger = new TestLogger(); +// var analysisId = Guid.NewGuid(); +// +// var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); +// +// var exception = new NullReferenceException("test message"); +// testSubject.AnalysisFailed(exception); +// +// var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisFailed, filePath, analysisId, exception); +// logger.AssertPartialOutputStringExists(analyzerName); +// logger.AssertPartialOutputStringExists(expectedMessage); +// logger.OutputStrings.Count.Should().Be(1); +// } +// +// [TestMethod] +// public void AnalysisFailed_FailureMessage_LogToOutputWindow() +// { +// const string analyzerName = "some analyzer"; +// const string filePath = "c:\\test\\foo-started.cpp"; +// var logger = new TestLogger(); +// var analysisId = Guid.NewGuid(); +// +// var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); +// +// testSubject.AnalysisFailed("test message"); +// +// var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisFailed, filePath, analysisId, "test message"); +// logger.AssertPartialOutputStringExists(analyzerName); +// logger.AssertPartialOutputStringExists(expectedMessage); +// logger.OutputStrings.Count.Should().Be(1); +// } +// +// [TestMethod] +// public void AnalysisFailed_AggregateException_LogToOutputWindow() +// { +// const string analyzerName = "some analyzer"; +// const string filePath = "c:\\test\\foo-started.cpp"; +// var logger = new TestLogger(); +// +// var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), analyzerName, logger: logger); +// +// var exception = new AggregateException( +// new List +// { +// new ArgumentNullException("this is a test1"), +// new NotImplementedException("this is a test2") +// }); +// +// testSubject.AnalysisFailed(exception); +// +// logger.AssertPartialOutputStringExists(analyzerName); +// logger.AssertPartialOutputStringExists("this is a test1"); +// logger.AssertPartialOutputStringExists("this is a test2"); +// logger.OutputStrings.Count.Should().Be(1); +// } +// +// private void VerifyStatusBarMessageAndIcon(Mock statusBarMock, string expectedMessage, bool isSpinnerOn) +// { +// statusBarMock.Verify(x=> x.Notify(expectedMessage, isSpinnerOn), Times.Once); +// statusBarMock.VerifyNoOtherCalls(); +// } +// +// private AnalysisStatusNotifier CreateTestSubject(string filePath, +// Guid? analysisId, +// string analyzerName = "analyzer", +// IStatusBarNotifier statusBarNotifier = null, +// ILogger logger = null) +// { +// statusBarNotifier ??= Mock.Of(); +// logger ??= new TestLogger(); +// +// return new AnalysisStatusNotifier(analyzerName, filePath, analysisId, statusBarNotifier, logger); +// } +// } +// } diff --git a/src/Integration.Vsix/Analysis/AnalysisStatusNotifier.cs b/src/Integration.Vsix/Analysis/AnalysisStatusNotifier.cs index 0b0cdd3c0b..685bcda64f 100644 --- a/src/Integration.Vsix/Analysis/AnalysisStatusNotifier.cs +++ b/src/Integration.Vsix/Analysis/AnalysisStatusNotifier.cs @@ -50,10 +50,15 @@ public void AnalysisStarted() Notify(AnalysisStrings.Notifier_AnalysisStarted, true); } - public void AnalysisFinished(int issueCount, TimeSpan analysisTime) + public void AnalysisProgressed( + int issueCount, + string findingType, + bool isIntermediate) => + Log(AnalysisStrings.MSG_FoundIssues, issueCount, findingType, filePath, analysisId, !isIntermediate); + + public void AnalysisFinished(TimeSpan analysisTime) { Log(AnalysisStrings.MSG_AnalysisComplete, filePath, analysisId, Math.Round(analysisTime.TotalSeconds, 3)); - Log(AnalysisStrings.MSG_FoundIssues, issueCount, filePath); Notify(AnalysisStrings.Notifier_AnalysisFinished, false); } @@ -61,7 +66,7 @@ public void AnalysisFinished(int issueCount, TimeSpan analysisTime) public void AnalysisCancelled() { Log(AnalysisStrings.MSG_AnalysisAborted, filePath, analysisId); - + Notify("", false); } @@ -80,7 +85,7 @@ public void AnalysisFailed(string failureMessage) public void AnalysisNotReady(string reason) { Log(AnalysisStrings.MSG_AnalysisNotReady, filePath, analysisId, reason); - + Notify("", false); } diff --git a/src/Integration.Vsix/Analysis/AnalysisStrings.Designer.cs b/src/Integration.Vsix/Analysis/AnalysisStrings.Designer.cs index 0b70f38c19..69cbe983ab 100644 --- a/src/Integration.Vsix/Analysis/AnalysisStrings.Designer.cs +++ b/src/Integration.Vsix/Analysis/AnalysisStrings.Designer.cs @@ -1,7 +1,6 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -180,7 +179,7 @@ internal static string MSG_AnalysisStarted { } /// - /// Looks up a localized string similar to Found {0} issue(s) for {1}. + /// Looks up a localized string similar to Found {0} {1}(s) in {2} [id: {3}, final: {4}]. /// internal static string MSG_FoundIssues { get { diff --git a/src/Integration.Vsix/Analysis/AnalysisStrings.resx b/src/Integration.Vsix/Analysis/AnalysisStrings.resx index db4a804e97..58ad21366a 100644 --- a/src/Integration.Vsix/Analysis/AnalysisStrings.resx +++ b/src/Integration.Vsix/Analysis/AnalysisStrings.resx @@ -170,7 +170,7 @@ Analyzing {0} with id {1} - Found {0} issue(s) for {1} + Found {0} {1}(s) in {2} [id: {3}, final: {4}] Binding has changed. Open documents will be re-analysed. diff --git a/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs b/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs index 8c3f6ebb3f..38ee856fbd 100644 --- a/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs +++ b/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs @@ -1,306 +1,306 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Analysis; -using SonarLint.VisualStudio.SLCore.Analysis; -using SonarLint.VisualStudio.SLCore.Common.Models; -using SonarLint.VisualStudio.SLCore.Configuration; -using SonarLint.VisualStudio.SLCore.Listener.Analysis; -using SonarLint.VisualStudio.SLCore.Listener.Analysis.Models; -using SonarLint.VisualStudio.SLCore.Listeners.Implementation.Analysis; -using SonarLint.VisualStudio.SLCore.Protocol; -using SonarLint.VisualStudio.SLCore.Service.Rules.Models; -using CleanCodeAttribute = SonarLint.VisualStudio.SLCore.Common.Models.CleanCodeAttribute; -using IssueSeverity = SonarLint.VisualStudio.SLCore.Common.Models.IssueSeverity; -using SloopLanguage = SonarLint.VisualStudio.SLCore.Common.Models.Language; - -namespace SonarLint.VisualStudio.SLCore.Listeners.UnitTests.Implementation.Analysis; - -[TestClass] -public class RaisedFindingProcessorTests -{ - [TestMethod] - public void MefCtor_CheckIsExported() => - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); - - [TestMethod] - public void MefCtor_CheckIsSingleton() => - MefTestHelpers.CheckIsSingletonMefComponent(); - - [TestMethod] - public void RaiseFindings_AnalysisIdIsNull_Ignores() - { - var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", new Dictionary>(), false, null); - var publisher = Substitute.For(); - var raiseFindingParamsToAnalysisIssueConverter = Substitute.For(); - - var testSubject = CreateTestSubject(raiseFindingToAnalysisIssueConverter: raiseFindingParamsToAnalysisIssueConverter); - - testSubject.RaiseFinding(raiseFindingParams, publisher); - - publisher.DidNotReceiveWithAnyArgs().Publish(default, default, default); - raiseFindingParamsToAnalysisIssueConverter.DidNotReceive().GetAnalysisIssues(Arg.Any(), Arg.Any>()); - } - - [TestMethod] - public void RaiseFindings_NoFindings_Ignores() - { - var fileUri = new FileUri("file://C:/somefile"); - var analysisId = Guid.NewGuid(); - var findingsByFileUri = new Dictionary> { { fileUri, [] } }; - - var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); - var publisher = Substitute.For(); - var raiseFindingParamsToAnalysisIssueConverter = CreateConverter(findingsByFileUri.Single().Key, [], []); - - var testSubject = CreateTestSubject(raiseFindingToAnalysisIssueConverter: raiseFindingParamsToAnalysisIssueConverter); - - testSubject.RaiseFinding(raiseFindingParams, publisher); - - raiseFindingParamsToAnalysisIssueConverter.Received().GetAnalysisIssues(fileUri, Arg.Is>(x => !x.Any())); - publisher.Received().Publish(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); - } - - [TestMethod] - public void RaiseFindings_NoSupportedLanguages_PublishesEmpty() - { - var analysisId = Guid.NewGuid(); - var fileUri = new FileUri("file://C:/somefile"); - var findingsByFileUri = new Dictionary> - { { fileUri, [CreateTestFinding("csharpsquid:S100"), CreateTestFinding("csharpsquid:S101")] } }; - - var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); - var publisher = Substitute.For(); - IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = CreateConverter(findingsByFileUri.Single().Key, [], []); - - var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, fileUri.LocalPath, analysisId); - - var testSubject = CreateTestSubject(raiseFindingToAnalysisIssueConverter: raiseFindingToAnalysisIssueConverter, - analysisStatusNotifierFactory: analysisStatusNotifierFactory, - slCoreConstantsProvider: CreateConstantsProviderWithLanguages([])); - - testSubject.RaiseFinding(raiseFindingParams, publisher); - - raiseFindingToAnalysisIssueConverter.Received().GetAnalysisIssues(fileUri, Arg.Is>(x => !x.Any())); - publisher.Received().Publish(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); - analysisStatusNotifier.AnalysisFinished(0, TimeSpan.Zero); - } - - [TestMethod] - public void RaiseFindings_NoKnownLanguages_PublishesEmpty() - { - var analysisId = Guid.NewGuid(); - var fileUri = new FileUri("file://C:/somefile"); - var findingsByFileUri = new Dictionary> - { { fileUri, [CreateTestFinding("csharpsquid:S100"), CreateTestFinding("csharpsquid:S101")] } }; - - var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); - var publisher = Substitute.For(); - IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = CreateConverter(findingsByFileUri.Single().Key, [], []); - - var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, fileUri.LocalPath, analysisId); - - var testSubject = CreateTestSubject( - raiseFindingToAnalysisIssueConverter: raiseFindingToAnalysisIssueConverter, - analysisStatusNotifierFactory: analysisStatusNotifierFactory, - slCoreConstantsProvider: CreateConstantsProviderWithLanguages([SloopLanguage.JAVA])); - - testSubject.RaiseFinding(raiseFindingParams, publisher); - - raiseFindingToAnalysisIssueConverter.Received().GetAnalysisIssues(fileUri, Arg.Is>(x => !x.Any())); - publisher.Received().Publish(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); - analysisStatusNotifier.AnalysisFinished(0, TimeSpan.Zero); - } - - [TestMethod] - public void RaiseFindings_HasNoFileUri_FinishesAnalysis() - { - var analysisId = Guid.NewGuid(); - var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, null, analysisId); - var publisher = Substitute.For(); - var testSubject = CreateTestSubject(analysisStatusNotifierFactory: analysisStatusNotifierFactory); - - var act = () => - testSubject.RaiseFinding(new RaiseFindingParams("CONFIGURATION_ID", new Dictionary>(), false, - analysisId), publisher); - - act.Should().NotThrow(); - publisher.ReceivedCalls().Should().BeEmpty(); - analysisStatusNotifier.ReceivedCalls().Should().BeEmpty(); - } - - [TestMethod] - public void RaiseFindings_HasIssuesNotIntermediate_PublishFindings() - { - var analysisId = Guid.NewGuid(); - var fileUri = new FileUri("file://C:/somefile"); - var analysisIssue1 = CreateAnalysisIssue("csharpsquid:S100"); - var analysisIssue2 = CreateAnalysisIssue("secrets:S1012"); - var filteredIssues = new[] { analysisIssue1, analysisIssue2 }; - - var raisedFinding1 = CreateTestFinding("csharpsquid:S100"); - var raisedFinding2 = CreateTestFinding("javascript:S101"); - var raisedFinding3 = CreateTestFinding("secrets:S1012"); - var filteredRaisedFindings = new[] { raisedFinding1, raisedFinding3 }; - var raisedFindings = new List { raisedFinding1, raisedFinding2, raisedFinding3 }; - - var findingsByFileUri = new Dictionary> { { fileUri, raisedFindings } }; - - var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); - var publisher = Substitute.For(); - IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = - CreateConverter(findingsByFileUri.Single().Key, filteredRaisedFindings, filteredIssues); - - var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, fileUri.LocalPath, analysisId); - - var testSubject = CreateTestSubject( - raiseFindingToAnalysisIssueConverter: raiseFindingToAnalysisIssueConverter, - analysisStatusNotifierFactory: analysisStatusNotifierFactory, - slCoreConstantsProvider: CreateConstantsProviderWithLanguages(SloopLanguage.SECRETS, SloopLanguage.CS)); - - testSubject.RaiseFinding(raiseFindingParams, publisher); - - publisher.Received(1).Publish(fileUri.LocalPath, analysisId, filteredIssues); - raiseFindingToAnalysisIssueConverter.Received(1).GetAnalysisIssues(findingsByFileUri.Single().Key, Arg.Is>( - x => x.SequenceEqual(filteredRaisedFindings))); - - analysisStatusNotifierFactory.Received(1).Create("SLCoreAnalyzer", fileUri.LocalPath, analysisId); - analysisStatusNotifier.Received(1).AnalysisFinished(2, TimeSpan.Zero); - } - - [DataRow(true)] - [DataRow(false)] - [DataTestMethod] - public void RaiseFindings_MultipleFiles_PublishFindingsForEachFile(bool isIntermediate) - { - var analysisId = Guid.NewGuid(); - var fileUri1 = new FileUri("file://C:/somefile"); - var fileUri2 = new FileUri("file://C:/someOtherfile"); - var analysisIssue1 = CreateAnalysisIssue("secrets:S100"); - var analysisIssue2 = CreateAnalysisIssue("secrets:S101"); - - var raisedFinding1 = CreateTestFinding("secrets:S100"); - var raisedFinding2 = CreateTestFinding("secrets:S101"); - - var findingsByFileUri = new Dictionary> { { fileUri1, [raisedFinding1] }, { fileUri2, [raisedFinding2] } }; - - var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, isIntermediate, analysisId); - - var publisher = Substitute.For(); - var raiseFindingParamsToAnalysisIssueConverter = Substitute.For(); - raiseFindingParamsToAnalysisIssueConverter.GetAnalysisIssues(fileUri1, Arg.Any>()).Returns([analysisIssue1]); - raiseFindingParamsToAnalysisIssueConverter.GetAnalysisIssues(fileUri2, Arg.Any>()).Returns([analysisIssue2]); - - var analysisStatusNotifierFactory = Substitute.For(); - - var testSubject = CreateTestSubject( - raiseFindingToAnalysisIssueConverter: raiseFindingParamsToAnalysisIssueConverter, - analysisStatusNotifierFactory: analysisStatusNotifierFactory); - - testSubject.RaiseFinding(raiseFindingParams, publisher); - - publisher.Received(1).Publish(fileUri1.LocalPath, analysisId, - Arg.Is>(x => x.SequenceEqual(new List { analysisIssue1 }))); - publisher.Received(1).Publish(fileUri2.LocalPath, analysisId, - Arg.Is>(x => x.SequenceEqual(new List { analysisIssue2 }))); - - analysisStatusNotifierFactory.Received(1).Create("SLCoreAnalyzer", fileUri1.LocalPath, analysisId); - analysisStatusNotifierFactory.Received(1).Create("SLCoreAnalyzer", fileUri2.LocalPath, analysisId); - } - - private RaisedFindingProcessor CreateTestSubject( - IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = null, - IAnalysisStatusNotifierFactory analysisStatusNotifierFactory = null, - ILogger logger = null, - ISLCoreConstantsProvider slCoreConstantsProvider = null) - => new( - slCoreConstantsProvider ?? - CreateConstantsProviderWithLanguages(SloopLanguage.SECRETS, SloopLanguage.JS, SloopLanguage.TS, SloopLanguage.CSS), - raiseFindingToAnalysisIssueConverter ?? Substitute.For(), - analysisStatusNotifierFactory ?? Substitute.For(), logger ?? new TestLogger()); - - private static IRaiseFindingToAnalysisIssueConverter CreateConverter(FileUri fileUri, IReadOnlyCollection raisedFindingDtos, - IAnalysisIssue[] findings) - { - var raiseFindingParamsToAnalysisIssueConverter = Substitute.For(); - raiseFindingParamsToAnalysisIssueConverter - .GetAnalysisIssues(fileUri, Arg.Is>(x => x.SequenceEqual(raisedFindingDtos))).Returns(findings); - return raiseFindingParamsToAnalysisIssueConverter; - } - - private ISLCoreConstantsProvider CreateConstantsProviderWithLanguages(params SloopLanguage[] languages) - { - var slCoreConstantsProvider = Substitute.For(); - slCoreConstantsProvider.SLCoreAnalyzableLanguages.Returns(languages.ToList()); - return slCoreConstantsProvider; - } - - private IAnalysisStatusNotifierFactory CreateAnalysisStatusNotifierFactory(out IAnalysisStatusNotifier analysisStatusNotifier, string filePath, - Guid? analysisId) - { - var analysisStatusNotifierFactory = Substitute.For(); - analysisStatusNotifier = Substitute.For(); - analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), filePath, analysisId).Returns(analysisStatusNotifier); - return analysisStatusNotifierFactory; - } - - private TestFinding CreateTestFinding(string ruleKey) - { - return new TestFinding(default, default, ruleKey, default, default, default, default, default, default, default, default, default); - } - - private static IAnalysisIssue CreateAnalysisIssue(string ruleKey) - { - var analysisIssue1 = Substitute.For(); - analysisIssue1.RuleKey.Returns(ruleKey); - return analysisIssue1; - } - - private record TestFinding( - Guid id, - string serverKey, - string ruleKey, - string primaryMessage, - DateTimeOffset introductionDate, - bool isOnNewCode, - bool resolved, - TextRangeDto textRange, - List flows, - List quickFixes, - string ruleDescriptionContextKey, - Either severityMode) : - RaisedFindingDto(id, - serverKey, - ruleKey, - primaryMessage, - introductionDate, - isOnNewCode, - resolved, - textRange, - flows, - quickFixes, - ruleDescriptionContextKey, - severityMode); -} +// /* +// * SonarLint for Visual Studio +// * Copyright (C) 2016-2024 SonarSource SA +// * mailto:info AT sonarsource DOT com +// * +// * This program is free software; you can redistribute it and/or +// * modify it under the terms of the GNU Lesser General Public +// * License as published by the Free Software Foundation; either +// * version 3 of the License, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// * Lesser General Public License for more details. +// * +// * You should have received a copy of the GNU Lesser General Public License +// * along with this program; if not, write to the Free Software Foundation, +// * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// */ +// +// using SonarLint.VisualStudio.Core; +// using SonarLint.VisualStudio.Core.Analysis; +// using SonarLint.VisualStudio.SLCore.Analysis; +// using SonarLint.VisualStudio.SLCore.Common.Models; +// using SonarLint.VisualStudio.SLCore.Configuration; +// using SonarLint.VisualStudio.SLCore.Listener.Analysis; +// using SonarLint.VisualStudio.SLCore.Listener.Analysis.Models; +// using SonarLint.VisualStudio.SLCore.Listeners.Implementation.Analysis; +// using SonarLint.VisualStudio.SLCore.Protocol; +// using SonarLint.VisualStudio.SLCore.Service.Rules.Models; +// using CleanCodeAttribute = SonarLint.VisualStudio.SLCore.Common.Models.CleanCodeAttribute; +// using IssueSeverity = SonarLint.VisualStudio.SLCore.Common.Models.IssueSeverity; +// using SloopLanguage = SonarLint.VisualStudio.SLCore.Common.Models.Language; +// +// namespace SonarLint.VisualStudio.SLCore.Listeners.UnitTests.Implementation.Analysis; +// +// [TestClass] +// public class RaisedFindingProcessorTests +// { +// [TestMethod] +// public void MefCtor_CheckIsExported() => +// MefTestHelpers.CheckTypeCanBeImported( +// MefTestHelpers.CreateExport(), +// MefTestHelpers.CreateExport(), +// MefTestHelpers.CreateExport(), +// MefTestHelpers.CreateExport()); +// +// [TestMethod] +// public void MefCtor_CheckIsSingleton() => +// MefTestHelpers.CheckIsSingletonMefComponent(); +// +// [TestMethod] +// public void RaiseFindings_AnalysisIdIsNull_Ignores() +// { +// var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", new Dictionary>(), false, null); +// var publisher = Substitute.For(); +// var raiseFindingParamsToAnalysisIssueConverter = Substitute.For(); +// +// var testSubject = CreateTestSubject(raiseFindingToAnalysisIssueConverter: raiseFindingParamsToAnalysisIssueConverter); +// +// testSubject.RaiseFinding(raiseFindingParams, publisher); +// +// publisher.DidNotReceiveWithAnyArgs().Publish(default, default, default); +// raiseFindingParamsToAnalysisIssueConverter.DidNotReceive().GetAnalysisIssues(Arg.Any(), Arg.Any>()); +// } +// +// [TestMethod] +// public void RaiseFindings_NoFindings_Ignores() +// { +// var fileUri = new FileUri("file://C:/somefile"); +// var analysisId = Guid.NewGuid(); +// var findingsByFileUri = new Dictionary> { { fileUri, [] } }; +// +// var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); +// var publisher = Substitute.For(); +// var raiseFindingParamsToAnalysisIssueConverter = CreateConverter(findingsByFileUri.Single().Key, [], []); +// +// var testSubject = CreateTestSubject(raiseFindingToAnalysisIssueConverter: raiseFindingParamsToAnalysisIssueConverter); +// +// testSubject.RaiseFinding(raiseFindingParams, publisher); +// +// raiseFindingParamsToAnalysisIssueConverter.Received().GetAnalysisIssues(fileUri, Arg.Is>(x => !x.Any())); +// publisher.Received().Publish(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); +// } +// +// [TestMethod] +// public void RaiseFindings_NoSupportedLanguages_PublishesEmpty() +// { +// var analysisId = Guid.NewGuid(); +// var fileUri = new FileUri("file://C:/somefile"); +// var findingsByFileUri = new Dictionary> +// { { fileUri, [CreateTestFinding("csharpsquid:S100"), CreateTestFinding("csharpsquid:S101")] } }; +// +// var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); +// var publisher = Substitute.For(); +// IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = CreateConverter(findingsByFileUri.Single().Key, [], []); +// +// var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, fileUri.LocalPath, analysisId); +// +// var testSubject = CreateTestSubject(raiseFindingToAnalysisIssueConverter: raiseFindingToAnalysisIssueConverter, +// analysisStatusNotifierFactory: analysisStatusNotifierFactory, +// slCoreConstantsProvider: CreateConstantsProviderWithLanguages([])); +// +// testSubject.RaiseFinding(raiseFindingParams, publisher); +// +// raiseFindingToAnalysisIssueConverter.Received().GetAnalysisIssues(fileUri, Arg.Is>(x => !x.Any())); +// publisher.Received().Publish(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); +// analysisStatusNotifier.AnalysisFinished(0, TimeSpan.Zero); +// } +// +// [TestMethod] +// public void RaiseFindings_NoKnownLanguages_PublishesEmpty() +// { +// var analysisId = Guid.NewGuid(); +// var fileUri = new FileUri("file://C:/somefile"); +// var findingsByFileUri = new Dictionary> +// { { fileUri, [CreateTestFinding("csharpsquid:S100"), CreateTestFinding("csharpsquid:S101")] } }; +// +// var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); +// var publisher = Substitute.For(); +// IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = CreateConverter(findingsByFileUri.Single().Key, [], []); +// +// var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, fileUri.LocalPath, analysisId); +// +// var testSubject = CreateTestSubject( +// raiseFindingToAnalysisIssueConverter: raiseFindingToAnalysisIssueConverter, +// analysisStatusNotifierFactory: analysisStatusNotifierFactory, +// slCoreConstantsProvider: CreateConstantsProviderWithLanguages([SloopLanguage.JAVA])); +// +// testSubject.RaiseFinding(raiseFindingParams, publisher); +// +// raiseFindingToAnalysisIssueConverter.Received().GetAnalysisIssues(fileUri, Arg.Is>(x => !x.Any())); +// publisher.Received().Publish(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); +// analysisStatusNotifier.AnalysisFinished(0, TimeSpan.Zero); +// } +// +// [TestMethod] +// public void RaiseFindings_HasNoFileUri_FinishesAnalysis() +// { +// var analysisId = Guid.NewGuid(); +// var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, null, analysisId); +// var publisher = Substitute.For(); +// var testSubject = CreateTestSubject(analysisStatusNotifierFactory: analysisStatusNotifierFactory); +// +// var act = () => +// testSubject.RaiseFinding(new RaiseFindingParams("CONFIGURATION_ID", new Dictionary>(), false, +// analysisId), publisher); +// +// act.Should().NotThrow(); +// publisher.ReceivedCalls().Should().BeEmpty(); +// analysisStatusNotifier.ReceivedCalls().Should().BeEmpty(); +// } +// +// [TestMethod] +// public void RaiseFindings_HasIssuesNotIntermediate_PublishFindings() +// { +// var analysisId = Guid.NewGuid(); +// var fileUri = new FileUri("file://C:/somefile"); +// var analysisIssue1 = CreateAnalysisIssue("csharpsquid:S100"); +// var analysisIssue2 = CreateAnalysisIssue("secrets:S1012"); +// var filteredIssues = new[] { analysisIssue1, analysisIssue2 }; +// +// var raisedFinding1 = CreateTestFinding("csharpsquid:S100"); +// var raisedFinding2 = CreateTestFinding("javascript:S101"); +// var raisedFinding3 = CreateTestFinding("secrets:S1012"); +// var filteredRaisedFindings = new[] { raisedFinding1, raisedFinding3 }; +// var raisedFindings = new List { raisedFinding1, raisedFinding2, raisedFinding3 }; +// +// var findingsByFileUri = new Dictionary> { { fileUri, raisedFindings } }; +// +// var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); +// var publisher = Substitute.For(); +// IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = +// CreateConverter(findingsByFileUri.Single().Key, filteredRaisedFindings, filteredIssues); +// +// var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, fileUri.LocalPath, analysisId); +// +// var testSubject = CreateTestSubject( +// raiseFindingToAnalysisIssueConverter: raiseFindingToAnalysisIssueConverter, +// analysisStatusNotifierFactory: analysisStatusNotifierFactory, +// slCoreConstantsProvider: CreateConstantsProviderWithLanguages(SloopLanguage.SECRETS, SloopLanguage.CS)); +// +// testSubject.RaiseFinding(raiseFindingParams, publisher); +// +// publisher.Received(1).Publish(fileUri.LocalPath, analysisId, filteredIssues); +// raiseFindingToAnalysisIssueConverter.Received(1).GetAnalysisIssues(findingsByFileUri.Single().Key, Arg.Is>( +// x => x.SequenceEqual(filteredRaisedFindings))); +// +// analysisStatusNotifierFactory.Received(1).Create("SLCoreAnalyzer", fileUri.LocalPath, analysisId); +// analysisStatusNotifier.Received(1).AnalysisFinished(2, TimeSpan.Zero); +// } +// +// [DataRow(true)] +// [DataRow(false)] +// [DataTestMethod] +// public void RaiseFindings_MultipleFiles_PublishFindingsForEachFile(bool isIntermediate) +// { +// var analysisId = Guid.NewGuid(); +// var fileUri1 = new FileUri("file://C:/somefile"); +// var fileUri2 = new FileUri("file://C:/someOtherfile"); +// var analysisIssue1 = CreateAnalysisIssue("secrets:S100"); +// var analysisIssue2 = CreateAnalysisIssue("secrets:S101"); +// +// var raisedFinding1 = CreateTestFinding("secrets:S100"); +// var raisedFinding2 = CreateTestFinding("secrets:S101"); +// +// var findingsByFileUri = new Dictionary> { { fileUri1, [raisedFinding1] }, { fileUri2, [raisedFinding2] } }; +// +// var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, isIntermediate, analysisId); +// +// var publisher = Substitute.For(); +// var raiseFindingParamsToAnalysisIssueConverter = Substitute.For(); +// raiseFindingParamsToAnalysisIssueConverter.GetAnalysisIssues(fileUri1, Arg.Any>()).Returns([analysisIssue1]); +// raiseFindingParamsToAnalysisIssueConverter.GetAnalysisIssues(fileUri2, Arg.Any>()).Returns([analysisIssue2]); +// +// var analysisStatusNotifierFactory = Substitute.For(); +// +// var testSubject = CreateTestSubject( +// raiseFindingToAnalysisIssueConverter: raiseFindingParamsToAnalysisIssueConverter, +// analysisStatusNotifierFactory: analysisStatusNotifierFactory); +// +// testSubject.RaiseFinding(raiseFindingParams, publisher); +// +// publisher.Received(1).Publish(fileUri1.LocalPath, analysisId, +// Arg.Is>(x => x.SequenceEqual(new List { analysisIssue1 }))); +// publisher.Received(1).Publish(fileUri2.LocalPath, analysisId, +// Arg.Is>(x => x.SequenceEqual(new List { analysisIssue2 }))); +// +// analysisStatusNotifierFactory.Received(1).Create("SLCoreAnalyzer", fileUri1.LocalPath, analysisId); +// analysisStatusNotifierFactory.Received(1).Create("SLCoreAnalyzer", fileUri2.LocalPath, analysisId); +// } +// +// private RaisedFindingProcessor CreateTestSubject( +// IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = null, +// IAnalysisStatusNotifierFactory analysisStatusNotifierFactory = null, +// ILogger logger = null, +// ISLCoreConstantsProvider slCoreConstantsProvider = null) +// => new( +// slCoreConstantsProvider ?? +// CreateConstantsProviderWithLanguages(SloopLanguage.SECRETS, SloopLanguage.JS, SloopLanguage.TS, SloopLanguage.CSS), +// raiseFindingToAnalysisIssueConverter ?? Substitute.For(), +// analysisStatusNotifierFactory ?? Substitute.For(), logger ?? new TestLogger()); +// +// private static IRaiseFindingToAnalysisIssueConverter CreateConverter(FileUri fileUri, IReadOnlyCollection raisedFindingDtos, +// IAnalysisIssue[] findings) +// { +// var raiseFindingParamsToAnalysisIssueConverter = Substitute.For(); +// raiseFindingParamsToAnalysisIssueConverter +// .GetAnalysisIssues(fileUri, Arg.Is>(x => x.SequenceEqual(raisedFindingDtos))).Returns(findings); +// return raiseFindingParamsToAnalysisIssueConverter; +// } +// +// private ISLCoreConstantsProvider CreateConstantsProviderWithLanguages(params SloopLanguage[] languages) +// { +// var slCoreConstantsProvider = Substitute.For(); +// slCoreConstantsProvider.SLCoreAnalyzableLanguages.Returns(languages.ToList()); +// return slCoreConstantsProvider; +// } +// +// private IAnalysisStatusNotifierFactory CreateAnalysisStatusNotifierFactory(out IAnalysisStatusNotifier analysisStatusNotifier, string filePath, +// Guid? analysisId) +// { +// var analysisStatusNotifierFactory = Substitute.For(); +// analysisStatusNotifier = Substitute.For(); +// analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), filePath, analysisId).Returns(analysisStatusNotifier); +// return analysisStatusNotifierFactory; +// } +// +// private TestFinding CreateTestFinding(string ruleKey) +// { +// return new TestFinding(default, default, ruleKey, default, default, default, default, default, default, default, default, default); +// } +// +// private static IAnalysisIssue CreateAnalysisIssue(string ruleKey) +// { +// var analysisIssue1 = Substitute.For(); +// analysisIssue1.RuleKey.Returns(ruleKey); +// return analysisIssue1; +// } +// +// private record TestFinding( +// Guid id, +// string serverKey, +// string ruleKey, +// string primaryMessage, +// DateTimeOffset introductionDate, +// bool isOnNewCode, +// bool resolved, +// TextRangeDto textRange, +// List flows, +// List quickFixes, +// string ruleDescriptionContextKey, +// Either severityMode) : +// RaisedFindingDto(id, +// serverKey, +// ruleKey, +// primaryMessage, +// introductionDate, +// isOnNewCode, +// resolved, +// textRange, +// flows, +// quickFixes, +// ruleDescriptionContextKey, +// severityMode); +// } diff --git a/src/SLCore.Listeners/Implementation/Analysis/RaisedFindingProcessor.cs b/src/SLCore.Listeners/Implementation/Analysis/RaisedFindingProcessor.cs index 53e93b6c2e..c0430d285c 100644 --- a/src/SLCore.Listeners/Implementation/Analysis/RaisedFindingProcessor.cs +++ b/src/SLCore.Listeners/Implementation/Analysis/RaisedFindingProcessor.cs @@ -85,7 +85,7 @@ private void PublishFindings(RaiseFindingParams parameters, IFindingsPubli findingsPublisher.Publish(localPath, parameters.analysisId!.Value, raiseFindingToAnalysisIssueConverter.GetAnalysisIssues(fileUri, supportedRaisedIssues)); - analysisStatusNotifier.AnalysisFinished(supportedRaisedIssues.Length, TimeSpan.Zero); + analysisStatusNotifier.AnalysisProgressed(supportedRaisedIssues.Length, findingsPublisher.FindingsType, parameters.isIntermediatePublication); } } diff --git a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs index b49d05faf7..82de2d3f44 100644 --- a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs +++ b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs @@ -1,300 +1,300 @@ -/* - * SonarLint for Visual Studio - * Copyright (C) 2016-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -using NSubstitute.ExceptionExtensions; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Analysis; -using SonarLint.VisualStudio.Core.CFamily; -using SonarLint.VisualStudio.Core.ConfigurationScope; -using SonarLint.VisualStudio.Core.SystemAbstractions; -using SonarLint.VisualStudio.SLCore.Analysis; -using SonarLint.VisualStudio.SLCore.Common.Models; -using SonarLint.VisualStudio.SLCore.Core; -using SonarLint.VisualStudio.SLCore.Service.Analysis; - -namespace SonarLint.VisualStudio.SLCore.UnitTests.Analysis; - -[TestClass] -public class SLCoreAnalyzerTests -{ - private const string ConfigScopeId = "ConfigScopeId"; - private const string FilePath = @"C:\file\path"; - private Guid analysisId; - private ISLCoreServiceProvider slCoreServiceProvider; - private IAnalysisSLCoreService analysisService; - private IActiveConfigScopeTracker activeConfigScopeTracker; - private IAnalysisStatusNotifierFactory analysisStatusNotifierFactory; - private ICurrentTimeProvider currentTimeProvider; - private IAggregatingCompilationDatabaseProvider compilationDatabaseLocator; - private IAnalysisStatusNotifier notifier; - private ILogger logger; - private SLCoreAnalyzer testSubject; - - [TestInitialize] - public void TestInitialize() - { - analysisId = Guid.NewGuid(); - slCoreServiceProvider = Substitute.For(); - analysisService = Substitute.For(); - SetUpServiceProvider(); - activeConfigScopeTracker = Substitute.For(); - analysisStatusNotifierFactory = Substitute.For(); - notifier = Substitute.For(); - SetUpDefaultNotifier(); - currentTimeProvider = Substitute.For(); - compilationDatabaseLocator = Substitute.For(); - logger = new TestLogger(); - testSubject = new SLCoreAnalyzer(slCoreServiceProvider, - activeConfigScopeTracker, - analysisStatusNotifierFactory, - currentTimeProvider, - compilationDatabaseLocator, - logger); - - void SetUpDefaultNotifier() => analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), FilePath, analysisId).Returns(notifier); - } - - [TestMethod] - public void MefCtor_CheckIsExported() => - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); - - [TestMethod] - public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); - - [TestMethod] - public void ExecuteAnalysis_CreatesNotifierAndStarts() - { - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); - - analysisStatusNotifierFactory.Received().Create(nameof(SLCoreAnalyzer), FilePath, analysisId); - notifier.Received().AnalysisStarted(); - } - - [TestMethod] - public void ExecuteAnalysis_ConfigScopeNotInitialized_NotifyNotReady() - { - activeConfigScopeTracker.Current.Returns((ConfigurationScope)null); - - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); - - _ = activeConfigScopeTracker.Received().Current; - analysisService.ReceivedCalls().Should().BeEmpty(); - notifier.Received().AnalysisNotReady(SLCoreStrings.ConfigScopeNotInitialized); - } - - [TestMethod] - public void ExecuteAnalysis_ConfigScopeNotReadyForAnalysis_NotifyNotReady() - { - activeConfigScopeTracker.Current.Returns(new ConfigurationScope(ConfigScopeId, IsReadyForAnalysis: false)); - - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); - - _ = activeConfigScopeTracker.Received().Current; - analysisService.ReceivedCalls().Should().BeEmpty(); - notifier.Received().AnalysisNotReady(SLCoreStrings.ConfigScopeNotInitialized); - } - - [TestMethod] - public void ExecuteAnalysis_ServiceProviderUnavailable_NotifyFailed() - { - SetUpServiceProvider(false); - SetUpInitializedConfigScope(); - - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); - - slCoreServiceProvider.Received().TryGetTransientService(out Arg.Any()); - analysisService.ReceivedCalls().Should().BeEmpty(); - notifier.Received().AnalysisFailed(SLCoreStrings.ServiceProviderNotInitialized); - } - - [TestMethod] - public void ExecuteAnalysis_PassesCorrectArgumentsToAnalysisService() - { - var expectedTimeStamp = DateTimeOffset.Now; - SetUpCurrentTimeProvider(expectedTimeStamp); - SetUpInitializedConfigScope(); - - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); - - analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => - a.analysisId == analysisId - && a.configurationScopeId == ConfigScopeId - && a.filesToAnalyze.Single() == new FileUri(FilePath) - && a.extraProperties.Count == 0 - && a.startTime == expectedTimeStamp.ToUnixTimeMilliseconds()), - Arg.Any()); - } - - [DataTestMethod] - [DataRow(null, false)] - [DataRow(false, false)] - [DataRow(true, true)] - public void ExecuteAnalysis_ShouldFetchServerIssues_PassesCorrectValueToAnalysisService(bool? value, bool expected) - { - IAnalyzerOptions options = value.HasValue ? new AnalyzerOptions { IsOnOpen = value.Value } : null; - SetUpInitializedConfigScope(); - - testSubject.ExecuteAnalysis(FilePath, default, default, options, default); - - analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => - a.shouldFetchServerIssues == expected), - Arg.Any()); - } - - [TestMethod] - public void ExecuteAnalysis_ForCFamily_PassesCompilationDatabaseAsExtraProperties() - { - const string filePath = @"C:\file\path\myclass.cpp"; - const string compilationDatabasePath = @"C:\file\path\compilation_database.json"; - var compilationDatabaseHandle = CreateCompilationDatabaseHandle(compilationDatabasePath); - SetUpCompilationDatabaseLocator(filePath, compilationDatabaseHandle); - SetUpInitializedConfigScope(); - - testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default); - - analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => - a.extraProperties != null - && a.extraProperties["sonar.cfamily.compile-commands"] == compilationDatabasePath), - Arg.Any()); - compilationDatabaseHandle.Received().Dispose(); - } - - [TestMethod] - public void ExecuteAnalysis_ForCFamily_AnalysisThrows_CompilationDatabaaseDisposed() - { - const string filePath = @"C:\file\path\myclass.cpp"; - const string compilationDatabasePath = @"C:\file\path\compilation_database.json"; - var compilationDatabaseHandle = CreateCompilationDatabaseHandle(compilationDatabasePath); - SetUpCompilationDatabaseLocator(filePath, compilationDatabaseHandle); - SetUpInitializedConfigScope(); - analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(); - - testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default); - - compilationDatabaseHandle.Received().Dispose(); - } - - [TestMethod] - public void ExecuteAnalysis_ForCFamily_WithoutCompilationDatabase_PassesEmptyStringAsExtraProperty() - { - const string filePath = @"C:\file\path\myclass.cpp"; - SetUpCompilationDatabaseLocator(filePath, null); - SetUpInitializedConfigScope(); - - testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default); - - analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => - a.extraProperties != null - && a.extraProperties.ContainsKey("sonar.cfamily.compile-commands") - && a.extraProperties["sonar.cfamily.compile-commands"] == ""), - Arg.Any()); - } - - [TestMethod] - public void ExecuteAnalysis_PassesCorrectCancellationTokenToAnalysisService() - { - var cancellationTokenSource = new CancellationTokenSource(); - SetUpInitializedConfigScope(); - - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, cancellationTokenSource.Token); - - analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Any(), - cancellationTokenSource.Token); - } - - [TestMethod] - public void ExecuteAnalysis_AnalysisServiceSucceeds_ExitsWithoutFinishingAnalysis() - { - SetUpInitializedConfigScope(); - analysisService.AnalyzeFilesAndTrackAsync(default, default).ReturnsForAnyArgs(new AnalyzeFilesResponse(new HashSet(), [])); - - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); - - notifier.DidNotReceiveWithAnyArgs().AnalysisNotReady(default); - notifier.DidNotReceiveWithAnyArgs().AnalysisFailed(default(Exception)); - notifier.DidNotReceiveWithAnyArgs().AnalysisFailed(default(string)); - notifier.DidNotReceiveWithAnyArgs().AnalysisFinished(default, default); - } - - [TestMethod] - public void ExecuteAnalysis_AnalysisServiceFailsForFile_NotifyFailed() - { - SetUpInitializedConfigScope(); - analysisService.AnalyzeFilesAndTrackAsync(default, default).ReturnsForAnyArgs(new AnalyzeFilesResponse(new HashSet { new(@"C:\file\path") }, [])); - - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); - - notifier.Received().AnalysisFailed(SLCoreStrings.AnalysisFailedReason); - } - - [TestMethod] - public void ExecuteAnalysis_AnalysisServiceCancelled_NotifyCancel() - { - SetUpInitializedConfigScope(); - var operationCanceledException = new OperationCanceledException(); - analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(operationCanceledException); - - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); - - notifier.Received().AnalysisCancelled(); - } - - [TestMethod] - public void ExecuteAnalysis_AnalysisServiceThrows_NotifyFailed() - { - SetUpInitializedConfigScope(); - var exception = new Exception(); - analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(exception); - - testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); - - notifier.Received().AnalysisFailed(exception); - } - - private void SetUpServiceProvider(bool result = true) => - slCoreServiceProvider.TryGetTransientService(out Arg.Any()) - .Returns(info => - { - info[0] = analysisService; - return result; - }); - - private void SetUpInitializedConfigScope() => - activeConfigScopeTracker.Current.Returns(new ConfigurationScope(ConfigScopeId, IsReadyForAnalysis: true)); - - private void SetUpCurrentTimeProvider(DateTimeOffset nowTime) => - currentTimeProvider.Now.Returns(nowTime); - - private static ICompilationDatabaseHandle CreateCompilationDatabaseHandle(string compilationDatabasePath) - { - var handle = Substitute.For(); - handle.FilePath.Returns(compilationDatabasePath); - return handle; - } - - private void SetUpCompilationDatabaseLocator(string filePath, ICompilationDatabaseHandle handle) => - compilationDatabaseLocator.GetOrNull(filePath).Returns(handle); -} +// /* +// * SonarLint for Visual Studio +// * Copyright (C) 2016-2024 SonarSource SA +// * mailto:info AT sonarsource DOT com +// * +// * This program is free software; you can redistribute it and/or +// * modify it under the terms of the GNU Lesser General Public +// * License as published by the Free Software Foundation; either +// * version 3 of the License, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// * Lesser General Public License for more details. +// * +// * You should have received a copy of the GNU Lesser General Public License +// * along with this program; if not, write to the Free Software Foundation, +// * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// */ +// +// using NSubstitute.ExceptionExtensions; +// using SonarLint.VisualStudio.Core; +// using SonarLint.VisualStudio.Core.Analysis; +// using SonarLint.VisualStudio.Core.CFamily; +// using SonarLint.VisualStudio.Core.ConfigurationScope; +// using SonarLint.VisualStudio.Core.SystemAbstractions; +// using SonarLint.VisualStudio.SLCore.Analysis; +// using SonarLint.VisualStudio.SLCore.Common.Models; +// using SonarLint.VisualStudio.SLCore.Core; +// using SonarLint.VisualStudio.SLCore.Service.Analysis; +// +// namespace SonarLint.VisualStudio.SLCore.UnitTests.Analysis; +// +// [TestClass] +// public class SLCoreAnalyzerTests +// { +// private const string ConfigScopeId = "ConfigScopeId"; +// private const string FilePath = @"C:\file\path"; +// private Guid analysisId; +// private ISLCoreServiceProvider slCoreServiceProvider; +// private IAnalysisSLCoreService analysisService; +// private IActiveConfigScopeTracker activeConfigScopeTracker; +// private IAnalysisStatusNotifierFactory analysisStatusNotifierFactory; +// private ICurrentTimeProvider currentTimeProvider; +// private IAggregatingCompilationDatabaseProvider compilationDatabaseLocator; +// private IAnalysisStatusNotifier notifier; +// private ILogger logger; +// private SLCoreAnalyzer testSubject; +// +// [TestInitialize] +// public void TestInitialize() +// { +// analysisId = Guid.NewGuid(); +// slCoreServiceProvider = Substitute.For(); +// analysisService = Substitute.For(); +// SetUpServiceProvider(); +// activeConfigScopeTracker = Substitute.For(); +// analysisStatusNotifierFactory = Substitute.For(); +// notifier = Substitute.For(); +// SetUpDefaultNotifier(); +// currentTimeProvider = Substitute.For(); +// compilationDatabaseLocator = Substitute.For(); +// logger = new TestLogger(); +// testSubject = new SLCoreAnalyzer(slCoreServiceProvider, +// activeConfigScopeTracker, +// analysisStatusNotifierFactory, +// currentTimeProvider, +// compilationDatabaseLocator, +// logger); +// +// void SetUpDefaultNotifier() => analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), FilePath, analysisId).Returns(notifier); +// } +// +// [TestMethod] +// public void MefCtor_CheckIsExported() => +// MefTestHelpers.CheckTypeCanBeImported( +// MefTestHelpers.CreateExport(), +// MefTestHelpers.CreateExport(), +// MefTestHelpers.CreateExport(), +// MefTestHelpers.CreateExport(), +// MefTestHelpers.CreateExport(), +// MefTestHelpers.CreateExport()); +// +// [TestMethod] +// public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); +// +// [TestMethod] +// public void ExecuteAnalysis_CreatesNotifierAndStarts() +// { +// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); +// +// analysisStatusNotifierFactory.Received().Create(nameof(SLCoreAnalyzer), FilePath, analysisId); +// notifier.Received().AnalysisStarted(); +// } +// +// [TestMethod] +// public void ExecuteAnalysis_ConfigScopeNotInitialized_NotifyNotReady() +// { +// activeConfigScopeTracker.Current.Returns((ConfigurationScope)null); +// +// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); +// +// _ = activeConfigScopeTracker.Received().Current; +// analysisService.ReceivedCalls().Should().BeEmpty(); +// notifier.Received().AnalysisNotReady(SLCoreStrings.ConfigScopeNotInitialized); +// } +// +// [TestMethod] +// public void ExecuteAnalysis_ConfigScopeNotReadyForAnalysis_NotifyNotReady() +// { +// activeConfigScopeTracker.Current.Returns(new ConfigurationScope(ConfigScopeId, IsReadyForAnalysis: false)); +// +// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); +// +// _ = activeConfigScopeTracker.Received().Current; +// analysisService.ReceivedCalls().Should().BeEmpty(); +// notifier.Received().AnalysisNotReady(SLCoreStrings.ConfigScopeNotInitialized); +// } +// +// [TestMethod] +// public void ExecuteAnalysis_ServiceProviderUnavailable_NotifyFailed() +// { +// SetUpServiceProvider(false); +// SetUpInitializedConfigScope(); +// +// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); +// +// slCoreServiceProvider.Received().TryGetTransientService(out Arg.Any()); +// analysisService.ReceivedCalls().Should().BeEmpty(); +// notifier.Received().AnalysisFailed(SLCoreStrings.ServiceProviderNotInitialized); +// } +// +// [TestMethod] +// public void ExecuteAnalysis_PassesCorrectArgumentsToAnalysisService() +// { +// var expectedTimeStamp = DateTimeOffset.Now; +// SetUpCurrentTimeProvider(expectedTimeStamp); +// SetUpInitializedConfigScope(); +// +// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); +// +// analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => +// a.analysisId == analysisId +// && a.configurationScopeId == ConfigScopeId +// && a.filesToAnalyze.Single() == new FileUri(FilePath) +// && a.extraProperties.Count == 0 +// && a.startTime == expectedTimeStamp.ToUnixTimeMilliseconds()), +// Arg.Any()); +// } +// +// [DataTestMethod] +// [DataRow(null, false)] +// [DataRow(false, false)] +// [DataRow(true, true)] +// public void ExecuteAnalysis_ShouldFetchServerIssues_PassesCorrectValueToAnalysisService(bool? value, bool expected) +// { +// IAnalyzerOptions options = value.HasValue ? new AnalyzerOptions { IsOnOpen = value.Value } : null; +// SetUpInitializedConfigScope(); +// +// testSubject.ExecuteAnalysis(FilePath, default, default, options, default); +// +// analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => +// a.shouldFetchServerIssues == expected), +// Arg.Any()); +// } +// +// [TestMethod] +// public void ExecuteAnalysis_ForCFamily_PassesCompilationDatabaseAsExtraProperties() +// { +// const string filePath = @"C:\file\path\myclass.cpp"; +// const string compilationDatabasePath = @"C:\file\path\compilation_database.json"; +// var compilationDatabaseHandle = CreateCompilationDatabaseHandle(compilationDatabasePath); +// SetUpCompilationDatabaseLocator(filePath, compilationDatabaseHandle); +// SetUpInitializedConfigScope(); +// +// testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default); +// +// analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => +// a.extraProperties != null +// && a.extraProperties["sonar.cfamily.compile-commands"] == compilationDatabasePath), +// Arg.Any()); +// compilationDatabaseHandle.Received().Dispose(); +// } +// +// [TestMethod] +// public void ExecuteAnalysis_ForCFamily_AnalysisThrows_CompilationDatabaaseDisposed() +// { +// const string filePath = @"C:\file\path\myclass.cpp"; +// const string compilationDatabasePath = @"C:\file\path\compilation_database.json"; +// var compilationDatabaseHandle = CreateCompilationDatabaseHandle(compilationDatabasePath); +// SetUpCompilationDatabaseLocator(filePath, compilationDatabaseHandle); +// SetUpInitializedConfigScope(); +// analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(); +// +// testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default); +// +// compilationDatabaseHandle.Received().Dispose(); +// } +// +// [TestMethod] +// public void ExecuteAnalysis_ForCFamily_WithoutCompilationDatabase_PassesEmptyStringAsExtraProperty() +// { +// const string filePath = @"C:\file\path\myclass.cpp"; +// SetUpCompilationDatabaseLocator(filePath, null); +// SetUpInitializedConfigScope(); +// +// testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default); +// +// analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => +// a.extraProperties != null +// && a.extraProperties.ContainsKey("sonar.cfamily.compile-commands") +// && a.extraProperties["sonar.cfamily.compile-commands"] == ""), +// Arg.Any()); +// } +// +// [TestMethod] +// public void ExecuteAnalysis_PassesCorrectCancellationTokenToAnalysisService() +// { +// var cancellationTokenSource = new CancellationTokenSource(); +// SetUpInitializedConfigScope(); +// +// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, cancellationTokenSource.Token); +// +// analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Any(), +// cancellationTokenSource.Token); +// } +// +// [TestMethod] +// public void ExecuteAnalysis_AnalysisServiceSucceeds_ExitsWithoutFinishingAnalysis() +// { +// SetUpInitializedConfigScope(); +// analysisService.AnalyzeFilesAndTrackAsync(default, default).ReturnsForAnyArgs(new AnalyzeFilesResponse(new HashSet(), [])); +// +// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); +// +// notifier.DidNotReceiveWithAnyArgs().AnalysisNotReady(default); +// notifier.DidNotReceiveWithAnyArgs().AnalysisFailed(default(Exception)); +// notifier.DidNotReceiveWithAnyArgs().AnalysisFailed(default(string)); +// notifier.DidNotReceiveWithAnyArgs().AnalysisFinished(default, default); +// } +// +// [TestMethod] +// public void ExecuteAnalysis_AnalysisServiceFailsForFile_NotifyFailed() +// { +// SetUpInitializedConfigScope(); +// analysisService.AnalyzeFilesAndTrackAsync(default, default).ReturnsForAnyArgs(new AnalyzeFilesResponse(new HashSet { new(@"C:\file\path") }, [])); +// +// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); +// +// notifier.Received().AnalysisFailed(SLCoreStrings.AnalysisFailedReason); +// } +// +// [TestMethod] +// public void ExecuteAnalysis_AnalysisServiceCancelled_NotifyCancel() +// { +// SetUpInitializedConfigScope(); +// var operationCanceledException = new OperationCanceledException(); +// analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(operationCanceledException); +// +// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); +// +// notifier.Received().AnalysisCancelled(); +// } +// +// [TestMethod] +// public void ExecuteAnalysis_AnalysisServiceThrows_NotifyFailed() +// { +// SetUpInitializedConfigScope(); +// var exception = new Exception(); +// analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(exception); +// +// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); +// +// notifier.Received().AnalysisFailed(exception); +// } +// +// private void SetUpServiceProvider(bool result = true) => +// slCoreServiceProvider.TryGetTransientService(out Arg.Any()) +// .Returns(info => +// { +// info[0] = analysisService; +// return result; +// }); +// +// private void SetUpInitializedConfigScope() => +// activeConfigScopeTracker.Current.Returns(new ConfigurationScope(ConfigScopeId, IsReadyForAnalysis: true)); +// +// private void SetUpCurrentTimeProvider(DateTimeOffset nowTime) => +// currentTimeProvider.Now.Returns(nowTime); +// +// private static ICompilationDatabaseHandle CreateCompilationDatabaseHandle(string compilationDatabasePath) +// { +// var handle = Substitute.For(); +// handle.FilePath.Returns(compilationDatabasePath); +// return handle; +// } +// +// private void SetUpCompilationDatabaseLocator(string filePath, ICompilationDatabaseHandle handle) => +// compilationDatabaseLocator.GetOrNull(filePath).Returns(handle); +// } diff --git a/src/SLCore/Analysis/SLCoreAnalyzer.cs b/src/SLCore/Analysis/SLCoreAnalyzer.cs index d3598dba12..fb19f412f1 100644 --- a/src/SLCore/Analysis/SLCoreAnalyzer.cs +++ b/src/SLCore/Analysis/SLCoreAnalyzer.cs @@ -102,7 +102,9 @@ private async Task ExecuteAnalysisInternalAsync( Dictionary properties = []; using var temporaryResourcesHandle = EnrichPropertiesForCFamily(properties, path, detectedLanguages); - var (failedAnalysisFiles, _) = await analysisService.AnalyzeFilesAndTrackAsync( + Stopwatch stopwatch = Stopwatch.StartNew(); + + var (failedAnalysisFiles, issues) = await analysisService.AnalyzeFilesAndTrackAsync( new AnalyzeFilesAndTrackParams( configScopeId, analysisId, @@ -116,6 +118,10 @@ [new FileUri(path)], { analysisStatusNotifier.AnalysisFailed(SLCoreStrings.AnalysisFailedReason); } + else + { + analysisStatusNotifier.AnalysisFinished(stopwatch.Elapsed); + } } catch (OperationCanceledException) { From 327db8377ed31c7249147701dbda5e8e24a8fbf2 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Mon, 16 Dec 2024 16:53:52 +0100 Subject: [PATCH 2/4] upd --- .../Analysis/RaisedFindingProcessorTests.cs | 639 +++++++++--------- 1 file changed, 333 insertions(+), 306 deletions(-) diff --git a/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs b/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs index 38ee856fbd..35f55590ee 100644 --- a/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs +++ b/src/SLCore.Listeners.UnitTests/Implementation/Analysis/RaisedFindingProcessorTests.cs @@ -1,306 +1,333 @@ -// /* -// * SonarLint for Visual Studio -// * Copyright (C) 2016-2024 SonarSource SA -// * mailto:info AT sonarsource DOT com -// * -// * This program is free software; you can redistribute it and/or -// * modify it under the terms of the GNU Lesser General Public -// * License as published by the Free Software Foundation; either -// * version 3 of the License, or (at your option) any later version. -// * -// * This program is distributed in the hope that it will be useful, -// * but WITHOUT ANY WARRANTY; without even the implied warranty of -// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// * Lesser General Public License for more details. -// * -// * You should have received a copy of the GNU Lesser General Public License -// * along with this program; if not, write to the Free Software Foundation, -// * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -// */ -// -// using SonarLint.VisualStudio.Core; -// using SonarLint.VisualStudio.Core.Analysis; -// using SonarLint.VisualStudio.SLCore.Analysis; -// using SonarLint.VisualStudio.SLCore.Common.Models; -// using SonarLint.VisualStudio.SLCore.Configuration; -// using SonarLint.VisualStudio.SLCore.Listener.Analysis; -// using SonarLint.VisualStudio.SLCore.Listener.Analysis.Models; -// using SonarLint.VisualStudio.SLCore.Listeners.Implementation.Analysis; -// using SonarLint.VisualStudio.SLCore.Protocol; -// using SonarLint.VisualStudio.SLCore.Service.Rules.Models; -// using CleanCodeAttribute = SonarLint.VisualStudio.SLCore.Common.Models.CleanCodeAttribute; -// using IssueSeverity = SonarLint.VisualStudio.SLCore.Common.Models.IssueSeverity; -// using SloopLanguage = SonarLint.VisualStudio.SLCore.Common.Models.Language; -// -// namespace SonarLint.VisualStudio.SLCore.Listeners.UnitTests.Implementation.Analysis; -// -// [TestClass] -// public class RaisedFindingProcessorTests -// { -// [TestMethod] -// public void MefCtor_CheckIsExported() => -// MefTestHelpers.CheckTypeCanBeImported( -// MefTestHelpers.CreateExport(), -// MefTestHelpers.CreateExport(), -// MefTestHelpers.CreateExport(), -// MefTestHelpers.CreateExport()); -// -// [TestMethod] -// public void MefCtor_CheckIsSingleton() => -// MefTestHelpers.CheckIsSingletonMefComponent(); -// -// [TestMethod] -// public void RaiseFindings_AnalysisIdIsNull_Ignores() -// { -// var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", new Dictionary>(), false, null); -// var publisher = Substitute.For(); -// var raiseFindingParamsToAnalysisIssueConverter = Substitute.For(); -// -// var testSubject = CreateTestSubject(raiseFindingToAnalysisIssueConverter: raiseFindingParamsToAnalysisIssueConverter); -// -// testSubject.RaiseFinding(raiseFindingParams, publisher); -// -// publisher.DidNotReceiveWithAnyArgs().Publish(default, default, default); -// raiseFindingParamsToAnalysisIssueConverter.DidNotReceive().GetAnalysisIssues(Arg.Any(), Arg.Any>()); -// } -// -// [TestMethod] -// public void RaiseFindings_NoFindings_Ignores() -// { -// var fileUri = new FileUri("file://C:/somefile"); -// var analysisId = Guid.NewGuid(); -// var findingsByFileUri = new Dictionary> { { fileUri, [] } }; -// -// var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); -// var publisher = Substitute.For(); -// var raiseFindingParamsToAnalysisIssueConverter = CreateConverter(findingsByFileUri.Single().Key, [], []); -// -// var testSubject = CreateTestSubject(raiseFindingToAnalysisIssueConverter: raiseFindingParamsToAnalysisIssueConverter); -// -// testSubject.RaiseFinding(raiseFindingParams, publisher); -// -// raiseFindingParamsToAnalysisIssueConverter.Received().GetAnalysisIssues(fileUri, Arg.Is>(x => !x.Any())); -// publisher.Received().Publish(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); -// } -// -// [TestMethod] -// public void RaiseFindings_NoSupportedLanguages_PublishesEmpty() -// { -// var analysisId = Guid.NewGuid(); -// var fileUri = new FileUri("file://C:/somefile"); -// var findingsByFileUri = new Dictionary> -// { { fileUri, [CreateTestFinding("csharpsquid:S100"), CreateTestFinding("csharpsquid:S101")] } }; -// -// var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); -// var publisher = Substitute.For(); -// IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = CreateConverter(findingsByFileUri.Single().Key, [], []); -// -// var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, fileUri.LocalPath, analysisId); -// -// var testSubject = CreateTestSubject(raiseFindingToAnalysisIssueConverter: raiseFindingToAnalysisIssueConverter, -// analysisStatusNotifierFactory: analysisStatusNotifierFactory, -// slCoreConstantsProvider: CreateConstantsProviderWithLanguages([])); -// -// testSubject.RaiseFinding(raiseFindingParams, publisher); -// -// raiseFindingToAnalysisIssueConverter.Received().GetAnalysisIssues(fileUri, Arg.Is>(x => !x.Any())); -// publisher.Received().Publish(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); -// analysisStatusNotifier.AnalysisFinished(0, TimeSpan.Zero); -// } -// -// [TestMethod] -// public void RaiseFindings_NoKnownLanguages_PublishesEmpty() -// { -// var analysisId = Guid.NewGuid(); -// var fileUri = new FileUri("file://C:/somefile"); -// var findingsByFileUri = new Dictionary> -// { { fileUri, [CreateTestFinding("csharpsquid:S100"), CreateTestFinding("csharpsquid:S101")] } }; -// -// var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); -// var publisher = Substitute.For(); -// IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = CreateConverter(findingsByFileUri.Single().Key, [], []); -// -// var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, fileUri.LocalPath, analysisId); -// -// var testSubject = CreateTestSubject( -// raiseFindingToAnalysisIssueConverter: raiseFindingToAnalysisIssueConverter, -// analysisStatusNotifierFactory: analysisStatusNotifierFactory, -// slCoreConstantsProvider: CreateConstantsProviderWithLanguages([SloopLanguage.JAVA])); -// -// testSubject.RaiseFinding(raiseFindingParams, publisher); -// -// raiseFindingToAnalysisIssueConverter.Received().GetAnalysisIssues(fileUri, Arg.Is>(x => !x.Any())); -// publisher.Received().Publish(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); -// analysisStatusNotifier.AnalysisFinished(0, TimeSpan.Zero); -// } -// -// [TestMethod] -// public void RaiseFindings_HasNoFileUri_FinishesAnalysis() -// { -// var analysisId = Guid.NewGuid(); -// var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, null, analysisId); -// var publisher = Substitute.For(); -// var testSubject = CreateTestSubject(analysisStatusNotifierFactory: analysisStatusNotifierFactory); -// -// var act = () => -// testSubject.RaiseFinding(new RaiseFindingParams("CONFIGURATION_ID", new Dictionary>(), false, -// analysisId), publisher); -// -// act.Should().NotThrow(); -// publisher.ReceivedCalls().Should().BeEmpty(); -// analysisStatusNotifier.ReceivedCalls().Should().BeEmpty(); -// } -// -// [TestMethod] -// public void RaiseFindings_HasIssuesNotIntermediate_PublishFindings() -// { -// var analysisId = Guid.NewGuid(); -// var fileUri = new FileUri("file://C:/somefile"); -// var analysisIssue1 = CreateAnalysisIssue("csharpsquid:S100"); -// var analysisIssue2 = CreateAnalysisIssue("secrets:S1012"); -// var filteredIssues = new[] { analysisIssue1, analysisIssue2 }; -// -// var raisedFinding1 = CreateTestFinding("csharpsquid:S100"); -// var raisedFinding2 = CreateTestFinding("javascript:S101"); -// var raisedFinding3 = CreateTestFinding("secrets:S1012"); -// var filteredRaisedFindings = new[] { raisedFinding1, raisedFinding3 }; -// var raisedFindings = new List { raisedFinding1, raisedFinding2, raisedFinding3 }; -// -// var findingsByFileUri = new Dictionary> { { fileUri, raisedFindings } }; -// -// var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); -// var publisher = Substitute.For(); -// IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = -// CreateConverter(findingsByFileUri.Single().Key, filteredRaisedFindings, filteredIssues); -// -// var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, fileUri.LocalPath, analysisId); -// -// var testSubject = CreateTestSubject( -// raiseFindingToAnalysisIssueConverter: raiseFindingToAnalysisIssueConverter, -// analysisStatusNotifierFactory: analysisStatusNotifierFactory, -// slCoreConstantsProvider: CreateConstantsProviderWithLanguages(SloopLanguage.SECRETS, SloopLanguage.CS)); -// -// testSubject.RaiseFinding(raiseFindingParams, publisher); -// -// publisher.Received(1).Publish(fileUri.LocalPath, analysisId, filteredIssues); -// raiseFindingToAnalysisIssueConverter.Received(1).GetAnalysisIssues(findingsByFileUri.Single().Key, Arg.Is>( -// x => x.SequenceEqual(filteredRaisedFindings))); -// -// analysisStatusNotifierFactory.Received(1).Create("SLCoreAnalyzer", fileUri.LocalPath, analysisId); -// analysisStatusNotifier.Received(1).AnalysisFinished(2, TimeSpan.Zero); -// } -// -// [DataRow(true)] -// [DataRow(false)] -// [DataTestMethod] -// public void RaiseFindings_MultipleFiles_PublishFindingsForEachFile(bool isIntermediate) -// { -// var analysisId = Guid.NewGuid(); -// var fileUri1 = new FileUri("file://C:/somefile"); -// var fileUri2 = new FileUri("file://C:/someOtherfile"); -// var analysisIssue1 = CreateAnalysisIssue("secrets:S100"); -// var analysisIssue2 = CreateAnalysisIssue("secrets:S101"); -// -// var raisedFinding1 = CreateTestFinding("secrets:S100"); -// var raisedFinding2 = CreateTestFinding("secrets:S101"); -// -// var findingsByFileUri = new Dictionary> { { fileUri1, [raisedFinding1] }, { fileUri2, [raisedFinding2] } }; -// -// var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, isIntermediate, analysisId); -// -// var publisher = Substitute.For(); -// var raiseFindingParamsToAnalysisIssueConverter = Substitute.For(); -// raiseFindingParamsToAnalysisIssueConverter.GetAnalysisIssues(fileUri1, Arg.Any>()).Returns([analysisIssue1]); -// raiseFindingParamsToAnalysisIssueConverter.GetAnalysisIssues(fileUri2, Arg.Any>()).Returns([analysisIssue2]); -// -// var analysisStatusNotifierFactory = Substitute.For(); -// -// var testSubject = CreateTestSubject( -// raiseFindingToAnalysisIssueConverter: raiseFindingParamsToAnalysisIssueConverter, -// analysisStatusNotifierFactory: analysisStatusNotifierFactory); -// -// testSubject.RaiseFinding(raiseFindingParams, publisher); -// -// publisher.Received(1).Publish(fileUri1.LocalPath, analysisId, -// Arg.Is>(x => x.SequenceEqual(new List { analysisIssue1 }))); -// publisher.Received(1).Publish(fileUri2.LocalPath, analysisId, -// Arg.Is>(x => x.SequenceEqual(new List { analysisIssue2 }))); -// -// analysisStatusNotifierFactory.Received(1).Create("SLCoreAnalyzer", fileUri1.LocalPath, analysisId); -// analysisStatusNotifierFactory.Received(1).Create("SLCoreAnalyzer", fileUri2.LocalPath, analysisId); -// } -// -// private RaisedFindingProcessor CreateTestSubject( -// IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = null, -// IAnalysisStatusNotifierFactory analysisStatusNotifierFactory = null, -// ILogger logger = null, -// ISLCoreConstantsProvider slCoreConstantsProvider = null) -// => new( -// slCoreConstantsProvider ?? -// CreateConstantsProviderWithLanguages(SloopLanguage.SECRETS, SloopLanguage.JS, SloopLanguage.TS, SloopLanguage.CSS), -// raiseFindingToAnalysisIssueConverter ?? Substitute.For(), -// analysisStatusNotifierFactory ?? Substitute.For(), logger ?? new TestLogger()); -// -// private static IRaiseFindingToAnalysisIssueConverter CreateConverter(FileUri fileUri, IReadOnlyCollection raisedFindingDtos, -// IAnalysisIssue[] findings) -// { -// var raiseFindingParamsToAnalysisIssueConverter = Substitute.For(); -// raiseFindingParamsToAnalysisIssueConverter -// .GetAnalysisIssues(fileUri, Arg.Is>(x => x.SequenceEqual(raisedFindingDtos))).Returns(findings); -// return raiseFindingParamsToAnalysisIssueConverter; -// } -// -// private ISLCoreConstantsProvider CreateConstantsProviderWithLanguages(params SloopLanguage[] languages) -// { -// var slCoreConstantsProvider = Substitute.For(); -// slCoreConstantsProvider.SLCoreAnalyzableLanguages.Returns(languages.ToList()); -// return slCoreConstantsProvider; -// } -// -// private IAnalysisStatusNotifierFactory CreateAnalysisStatusNotifierFactory(out IAnalysisStatusNotifier analysisStatusNotifier, string filePath, -// Guid? analysisId) -// { -// var analysisStatusNotifierFactory = Substitute.For(); -// analysisStatusNotifier = Substitute.For(); -// analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), filePath, analysisId).Returns(analysisStatusNotifier); -// return analysisStatusNotifierFactory; -// } -// -// private TestFinding CreateTestFinding(string ruleKey) -// { -// return new TestFinding(default, default, ruleKey, default, default, default, default, default, default, default, default, default); -// } -// -// private static IAnalysisIssue CreateAnalysisIssue(string ruleKey) -// { -// var analysisIssue1 = Substitute.For(); -// analysisIssue1.RuleKey.Returns(ruleKey); -// return analysisIssue1; -// } -// -// private record TestFinding( -// Guid id, -// string serverKey, -// string ruleKey, -// string primaryMessage, -// DateTimeOffset introductionDate, -// bool isOnNewCode, -// bool resolved, -// TextRangeDto textRange, -// List flows, -// List quickFixes, -// string ruleDescriptionContextKey, -// Either severityMode) : -// RaisedFindingDto(id, -// serverKey, -// ruleKey, -// primaryMessage, -// introductionDate, -// isOnNewCode, -// resolved, -// textRange, -// flows, -// quickFixes, -// ruleDescriptionContextKey, -// severityMode); -// } +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.Analysis; +using SonarLint.VisualStudio.SLCore.Analysis; +using SonarLint.VisualStudio.SLCore.Common.Models; +using SonarLint.VisualStudio.SLCore.Configuration; +using SonarLint.VisualStudio.SLCore.Listener.Analysis; +using SonarLint.VisualStudio.SLCore.Listener.Analysis.Models; +using SonarLint.VisualStudio.SLCore.Listeners.Implementation.Analysis; +using SonarLint.VisualStudio.SLCore.Protocol; +using SonarLint.VisualStudio.SLCore.Service.Rules.Models; +using SloopLanguage = SonarLint.VisualStudio.SLCore.Common.Models.Language; + +namespace SonarLint.VisualStudio.SLCore.Listeners.UnitTests.Implementation.Analysis; + +[TestClass] +public class RaisedFindingProcessorTests +{ + private const string FindingsType = "FINDING"; + + [TestMethod] + public void MefCtor_CheckIsExported() => + MefTestHelpers.CheckTypeCanBeImported( + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport()); + + [TestMethod] + public void MefCtor_CheckIsSingleton() => + MefTestHelpers.CheckIsSingletonMefComponent(); + + [TestMethod] + public void RaiseFindings_AnalysisIdIsNull_Ignores() + { + var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", new Dictionary>(), false, null); + var publisher = Substitute.For(); + var raiseFindingParamsToAnalysisIssueConverter = Substitute.For(); + + var testSubject = CreateTestSubject(raiseFindingToAnalysisIssueConverter: raiseFindingParamsToAnalysisIssueConverter); + + testSubject.RaiseFinding(raiseFindingParams, publisher); + + publisher.DidNotReceiveWithAnyArgs().Publish(default, default, default); + raiseFindingParamsToAnalysisIssueConverter.DidNotReceive().GetAnalysisIssues(Arg.Any(), Arg.Any>()); + } + + [TestMethod] + public void RaiseFindings_NoFindings_Ignores() + { + var fileUri = new FileUri("file://C:/somefile"); + var analysisId = Guid.NewGuid(); + var findingsByFileUri = new Dictionary> { { fileUri, [] } }; + + var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, false, analysisId); + var publisher = Substitute.For(); + var raiseFindingParamsToAnalysisIssueConverter = CreateConverter(findingsByFileUri.Single().Key, [], []); + + var testSubject = CreateTestSubject(raiseFindingToAnalysisIssueConverter: raiseFindingParamsToAnalysisIssueConverter); + + testSubject.RaiseFinding(raiseFindingParams, publisher); + + raiseFindingParamsToAnalysisIssueConverter.Received().GetAnalysisIssues(fileUri, Arg.Is>(x => !x.Any())); + publisher.Received().Publish(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); + } + + [TestMethod] + public void RaiseFindings_NoSupportedLanguages_PublishesEmpty() + { + var analysisId = Guid.NewGuid(); + var fileUri = new FileUri("file://C:/somefile"); + var findingsByFileUri = new Dictionary> + { { fileUri, [CreateTestFinding("csharpsquid:S100"), CreateTestFinding("csharpsquid:S101")] } }; + + var isIntermediatePublication = false; + var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, isIntermediatePublication, analysisId); + var publisher = CreatePublisher(); + IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = CreateConverter(findingsByFileUri.Single().Key, [], []); + + var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, fileUri.LocalPath, analysisId); + + var testSubject = CreateTestSubject(raiseFindingToAnalysisIssueConverter: raiseFindingToAnalysisIssueConverter, + analysisStatusNotifierFactory: analysisStatusNotifierFactory, + slCoreConstantsProvider: CreateConstantsProviderWithLanguages([])); + + testSubject.RaiseFinding(raiseFindingParams, publisher); + + raiseFindingToAnalysisIssueConverter.Received().GetAnalysisIssues(fileUri, Arg.Is>(x => !x.Any())); + publisher.Received().Publish(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); + analysisStatusNotifier.DidNotReceiveWithAnyArgs().AnalysisFinished(default); + analysisStatusNotifier.Received().AnalysisProgressed(0, FindingsType, isIntermediatePublication); + } + + [TestMethod] + public void RaiseFindings_NoKnownLanguages_PublishesEmpty() + { + var analysisId = Guid.NewGuid(); + var fileUri = new FileUri("file://C:/somefile"); + var findingsByFileUri = new Dictionary> + { { fileUri, [CreateTestFinding("csharpsquid:S100"), CreateTestFinding("csharpsquid:S101")] } }; + + var isIntermediatePublication = false; + var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, isIntermediatePublication, analysisId); + var publisher = CreatePublisher(); + IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = CreateConverter(findingsByFileUri.Single().Key, [], []); + + var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, fileUri.LocalPath, analysisId); + + var testSubject = CreateTestSubject( + raiseFindingToAnalysisIssueConverter: raiseFindingToAnalysisIssueConverter, + analysisStatusNotifierFactory: analysisStatusNotifierFactory, + slCoreConstantsProvider: CreateConstantsProviderWithLanguages([SloopLanguage.JAVA])); + + testSubject.RaiseFinding(raiseFindingParams, publisher); + + raiseFindingToAnalysisIssueConverter.Received().GetAnalysisIssues(fileUri, Arg.Is>(x => !x.Any())); + publisher.Received().Publish(fileUri.LocalPath, analysisId, Arg.Is>(x => !x.Any())); + analysisStatusNotifier.DidNotReceiveWithAnyArgs().AnalysisFinished(default); + analysisStatusNotifier.Received().AnalysisProgressed(0, FindingsType, isIntermediatePublication); + } + + [TestMethod] + public void RaiseFindings_HasNoFileUri_FinishesAnalysis() + { + var analysisId = Guid.NewGuid(); + var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, null, analysisId); + var publisher = CreatePublisher(); + var testSubject = CreateTestSubject(analysisStatusNotifierFactory: analysisStatusNotifierFactory); + + var act = () => + testSubject.RaiseFinding(new RaiseFindingParams("CONFIGURATION_ID", new Dictionary>(), false, + analysisId), publisher); + + act.Should().NotThrow(); + publisher.ReceivedCalls().Should().BeEmpty(); + analysisStatusNotifier.ReceivedCalls().Should().BeEmpty(); + } + + [TestMethod] + public void RaiseFindings_HasIssuesNotIntermediate_PublishFindings() + { + var analysisId = Guid.NewGuid(); + var fileUri = new FileUri("file://C:/somefile"); + var analysisIssue1 = CreateAnalysisIssue("csharpsquid:S100"); + var analysisIssue2 = CreateAnalysisIssue("secrets:S1012"); + var filteredIssues = new[] { analysisIssue1, analysisIssue2 }; + + var raisedFinding1 = CreateTestFinding("csharpsquid:S100"); + var raisedFinding2 = CreateTestFinding("javascript:S101"); + var raisedFinding3 = CreateTestFinding("secrets:S1012"); + var filteredRaisedFindings = new[] { raisedFinding1, raisedFinding3 }; + var raisedFindings = new List { raisedFinding1, raisedFinding2, raisedFinding3 }; + + var findingsByFileUri = new Dictionary> { { fileUri, raisedFindings } }; + + var isIntermediatePublication = false; + var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, isIntermediatePublication, analysisId); + var publisher = CreatePublisher(); + IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = + CreateConverter(findingsByFileUri.Single().Key, filteredRaisedFindings, filteredIssues); + + var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var analysisStatusNotifier, fileUri.LocalPath, analysisId); + + var testSubject = CreateTestSubject( + raiseFindingToAnalysisIssueConverter: raiseFindingToAnalysisIssueConverter, + analysisStatusNotifierFactory: analysisStatusNotifierFactory, + slCoreConstantsProvider: CreateConstantsProviderWithLanguages(SloopLanguage.SECRETS, SloopLanguage.CS)); + + testSubject.RaiseFinding(raiseFindingParams, publisher); + + publisher.Received(1).Publish(fileUri.LocalPath, analysisId, filteredIssues); + raiseFindingToAnalysisIssueConverter.Received(1).GetAnalysisIssues(findingsByFileUri.Single().Key, Arg.Is>( + x => x.SequenceEqual(filteredRaisedFindings))); + + analysisStatusNotifierFactory.Received(1).Create("SLCoreAnalyzer", fileUri.LocalPath, analysisId); + analysisStatusNotifier.DidNotReceiveWithAnyArgs().AnalysisFinished(default); + analysisStatusNotifier.Received().AnalysisProgressed(2, FindingsType, isIntermediatePublication); + } + + [DataRow(true)] + [DataRow(false)] + [DataTestMethod] + public void RaiseFindings_MultipleFiles_PublishFindingsForEachFile(bool isIntermediate) + { + var analysisId = Guid.NewGuid(); + var fileUri1 = new FileUri("file://C:/somefile"); + var fileUri2 = new FileUri("file://C:/someOtherfile"); + var analysisIssue1 = CreateAnalysisIssue("secrets:S100"); + var analysisIssue2 = CreateAnalysisIssue("secrets:S101"); + + var raisedFinding1 = CreateTestFinding("secrets:S100"); + var raisedFinding2 = CreateTestFinding("secrets:S101"); + + var findingsByFileUri = new Dictionary> { { fileUri1, [raisedFinding1] }, { fileUri2, [raisedFinding2] } }; + + var raiseFindingParams = new RaiseFindingParams("CONFIGURATION_ID", findingsByFileUri, isIntermediate, analysisId); + + var publisher = CreatePublisher(); + var raiseFindingParamsToAnalysisIssueConverter = Substitute.For(); + raiseFindingParamsToAnalysisIssueConverter.GetAnalysisIssues(fileUri1, Arg.Any>()).Returns([analysisIssue1]); + raiseFindingParamsToAnalysisIssueConverter.GetAnalysisIssues(fileUri2, Arg.Any>()).Returns([analysisIssue2]); + + var analysisStatusNotifierFactory = CreateAnalysisStatusNotifierFactory(out var notifier1, fileUri1.LocalPath, analysisId); + SetUpNotifierForFile(out var notifier2, fileUri2.LocalPath, analysisId, analysisStatusNotifierFactory); + + var testSubject = CreateTestSubject( + raiseFindingToAnalysisIssueConverter: raiseFindingParamsToAnalysisIssueConverter, + analysisStatusNotifierFactory: analysisStatusNotifierFactory); + + testSubject.RaiseFinding(raiseFindingParams, publisher); + + publisher.Received(1).Publish(fileUri1.LocalPath, analysisId, + Arg.Is>(x => x.SequenceEqual(new List { analysisIssue1 }))); + publisher.Received(1).Publish(fileUri2.LocalPath, analysisId, + Arg.Is>(x => x.SequenceEqual(new List { analysisIssue2 }))); + + analysisStatusNotifierFactory.Received(1).Create("SLCoreAnalyzer", fileUri1.LocalPath, analysisId); + analysisStatusNotifierFactory.Received(1).Create("SLCoreAnalyzer", fileUri2.LocalPath, analysisId); + notifier1.DidNotReceiveWithAnyArgs().AnalysisFinished(default); + notifier1.Received().AnalysisProgressed(1, FindingsType, isIntermediate); + notifier2.DidNotReceiveWithAnyArgs().AnalysisFinished(default); + notifier2.Received().AnalysisProgressed(1, FindingsType, isIntermediate); + } + + private RaisedFindingProcessor CreateTestSubject( + IRaiseFindingToAnalysisIssueConverter raiseFindingToAnalysisIssueConverter = null, + IAnalysisStatusNotifierFactory analysisStatusNotifierFactory = null, + ILogger logger = null, + ISLCoreConstantsProvider slCoreConstantsProvider = null) + => new( + slCoreConstantsProvider ?? + CreateConstantsProviderWithLanguages(SloopLanguage.SECRETS, SloopLanguage.JS, SloopLanguage.TS, SloopLanguage.CSS), + raiseFindingToAnalysisIssueConverter ?? Substitute.For(), + analysisStatusNotifierFactory ?? Substitute.For(), logger ?? new TestLogger()); + + private static IRaiseFindingToAnalysisIssueConverter CreateConverter(FileUri fileUri, IReadOnlyCollection raisedFindingDtos, + IAnalysisIssue[] findings) + { + var raiseFindingParamsToAnalysisIssueConverter = Substitute.For(); + raiseFindingParamsToAnalysisIssueConverter + .GetAnalysisIssues(fileUri, Arg.Is>(x => x.SequenceEqual(raisedFindingDtos))).Returns(findings); + return raiseFindingParamsToAnalysisIssueConverter; + } + + private ISLCoreConstantsProvider CreateConstantsProviderWithLanguages(params SloopLanguage[] languages) + { + var slCoreConstantsProvider = Substitute.For(); + slCoreConstantsProvider.SLCoreAnalyzableLanguages.Returns(languages.ToList()); + return slCoreConstantsProvider; + } + + private IAnalysisStatusNotifierFactory CreateAnalysisStatusNotifierFactory(out IAnalysisStatusNotifier analysisStatusNotifier, string filePath, + Guid? analysisId) + { + var analysisStatusNotifierFactory = Substitute.For(); + SetUpNotifierForFile(out analysisStatusNotifier, filePath, analysisId, analysisStatusNotifierFactory); + return analysisStatusNotifierFactory; + } + + private static void SetUpNotifierForFile( + out IAnalysisStatusNotifier analysisStatusNotifier, + string filePath, + Guid? analysisId, + IAnalysisStatusNotifierFactory analysisStatusNotifierFactory) + { + analysisStatusNotifier = Substitute.For(); + analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), filePath, analysisId).Returns(analysisStatusNotifier); + } + + private TestFinding CreateTestFinding(string ruleKey) + { + return new TestFinding(default, default, ruleKey, default, default, default, default, default, default, default, default, default); + } + + private static IFindingsPublisher CreatePublisher() + { + var publisher = Substitute.For(); + publisher.FindingsType.Returns(FindingsType); + return publisher; + } + + private static IAnalysisIssue CreateAnalysisIssue(string ruleKey) + { + var analysisIssue1 = Substitute.For(); + analysisIssue1.RuleKey.Returns(ruleKey); + return analysisIssue1; + } + + private record TestFinding( + Guid id, + string serverKey, + string ruleKey, + string primaryMessage, + DateTimeOffset introductionDate, + bool isOnNewCode, + bool resolved, + TextRangeDto textRange, + List flows, + List quickFixes, + string ruleDescriptionContextKey, + Either severityMode) : + RaisedFindingDto(id, + serverKey, + ruleKey, + primaryMessage, + introductionDate, + isOnNewCode, + resolved, + textRange, + flows, + quickFixes, + ruleDescriptionContextKey, + severityMode); +} From 80afe5f62c8abb75b23cf9404feadd9ab727f90f Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Mon, 16 Dec 2024 16:55:41 +0100 Subject: [PATCH 3/4] upd --- .../Analysis/SLCoreAnalyzerTests.cs | 600 +++++++++--------- 1 file changed, 300 insertions(+), 300 deletions(-) diff --git a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs index 82de2d3f44..a5c2631859 100644 --- a/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs +++ b/src/SLCore.UnitTests/Analysis/SLCoreAnalyzerTests.cs @@ -1,300 +1,300 @@ -// /* -// * SonarLint for Visual Studio -// * Copyright (C) 2016-2024 SonarSource SA -// * mailto:info AT sonarsource DOT com -// * -// * This program is free software; you can redistribute it and/or -// * modify it under the terms of the GNU Lesser General Public -// * License as published by the Free Software Foundation; either -// * version 3 of the License, or (at your option) any later version. -// * -// * This program is distributed in the hope that it will be useful, -// * but WITHOUT ANY WARRANTY; without even the implied warranty of -// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// * Lesser General Public License for more details. -// * -// * You should have received a copy of the GNU Lesser General Public License -// * along with this program; if not, write to the Free Software Foundation, -// * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -// */ -// -// using NSubstitute.ExceptionExtensions; -// using SonarLint.VisualStudio.Core; -// using SonarLint.VisualStudio.Core.Analysis; -// using SonarLint.VisualStudio.Core.CFamily; -// using SonarLint.VisualStudio.Core.ConfigurationScope; -// using SonarLint.VisualStudio.Core.SystemAbstractions; -// using SonarLint.VisualStudio.SLCore.Analysis; -// using SonarLint.VisualStudio.SLCore.Common.Models; -// using SonarLint.VisualStudio.SLCore.Core; -// using SonarLint.VisualStudio.SLCore.Service.Analysis; -// -// namespace SonarLint.VisualStudio.SLCore.UnitTests.Analysis; -// -// [TestClass] -// public class SLCoreAnalyzerTests -// { -// private const string ConfigScopeId = "ConfigScopeId"; -// private const string FilePath = @"C:\file\path"; -// private Guid analysisId; -// private ISLCoreServiceProvider slCoreServiceProvider; -// private IAnalysisSLCoreService analysisService; -// private IActiveConfigScopeTracker activeConfigScopeTracker; -// private IAnalysisStatusNotifierFactory analysisStatusNotifierFactory; -// private ICurrentTimeProvider currentTimeProvider; -// private IAggregatingCompilationDatabaseProvider compilationDatabaseLocator; -// private IAnalysisStatusNotifier notifier; -// private ILogger logger; -// private SLCoreAnalyzer testSubject; -// -// [TestInitialize] -// public void TestInitialize() -// { -// analysisId = Guid.NewGuid(); -// slCoreServiceProvider = Substitute.For(); -// analysisService = Substitute.For(); -// SetUpServiceProvider(); -// activeConfigScopeTracker = Substitute.For(); -// analysisStatusNotifierFactory = Substitute.For(); -// notifier = Substitute.For(); -// SetUpDefaultNotifier(); -// currentTimeProvider = Substitute.For(); -// compilationDatabaseLocator = Substitute.For(); -// logger = new TestLogger(); -// testSubject = new SLCoreAnalyzer(slCoreServiceProvider, -// activeConfigScopeTracker, -// analysisStatusNotifierFactory, -// currentTimeProvider, -// compilationDatabaseLocator, -// logger); -// -// void SetUpDefaultNotifier() => analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), FilePath, analysisId).Returns(notifier); -// } -// -// [TestMethod] -// public void MefCtor_CheckIsExported() => -// MefTestHelpers.CheckTypeCanBeImported( -// MefTestHelpers.CreateExport(), -// MefTestHelpers.CreateExport(), -// MefTestHelpers.CreateExport(), -// MefTestHelpers.CreateExport(), -// MefTestHelpers.CreateExport(), -// MefTestHelpers.CreateExport()); -// -// [TestMethod] -// public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); -// -// [TestMethod] -// public void ExecuteAnalysis_CreatesNotifierAndStarts() -// { -// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); -// -// analysisStatusNotifierFactory.Received().Create(nameof(SLCoreAnalyzer), FilePath, analysisId); -// notifier.Received().AnalysisStarted(); -// } -// -// [TestMethod] -// public void ExecuteAnalysis_ConfigScopeNotInitialized_NotifyNotReady() -// { -// activeConfigScopeTracker.Current.Returns((ConfigurationScope)null); -// -// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); -// -// _ = activeConfigScopeTracker.Received().Current; -// analysisService.ReceivedCalls().Should().BeEmpty(); -// notifier.Received().AnalysisNotReady(SLCoreStrings.ConfigScopeNotInitialized); -// } -// -// [TestMethod] -// public void ExecuteAnalysis_ConfigScopeNotReadyForAnalysis_NotifyNotReady() -// { -// activeConfigScopeTracker.Current.Returns(new ConfigurationScope(ConfigScopeId, IsReadyForAnalysis: false)); -// -// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); -// -// _ = activeConfigScopeTracker.Received().Current; -// analysisService.ReceivedCalls().Should().BeEmpty(); -// notifier.Received().AnalysisNotReady(SLCoreStrings.ConfigScopeNotInitialized); -// } -// -// [TestMethod] -// public void ExecuteAnalysis_ServiceProviderUnavailable_NotifyFailed() -// { -// SetUpServiceProvider(false); -// SetUpInitializedConfigScope(); -// -// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); -// -// slCoreServiceProvider.Received().TryGetTransientService(out Arg.Any()); -// analysisService.ReceivedCalls().Should().BeEmpty(); -// notifier.Received().AnalysisFailed(SLCoreStrings.ServiceProviderNotInitialized); -// } -// -// [TestMethod] -// public void ExecuteAnalysis_PassesCorrectArgumentsToAnalysisService() -// { -// var expectedTimeStamp = DateTimeOffset.Now; -// SetUpCurrentTimeProvider(expectedTimeStamp); -// SetUpInitializedConfigScope(); -// -// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); -// -// analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => -// a.analysisId == analysisId -// && a.configurationScopeId == ConfigScopeId -// && a.filesToAnalyze.Single() == new FileUri(FilePath) -// && a.extraProperties.Count == 0 -// && a.startTime == expectedTimeStamp.ToUnixTimeMilliseconds()), -// Arg.Any()); -// } -// -// [DataTestMethod] -// [DataRow(null, false)] -// [DataRow(false, false)] -// [DataRow(true, true)] -// public void ExecuteAnalysis_ShouldFetchServerIssues_PassesCorrectValueToAnalysisService(bool? value, bool expected) -// { -// IAnalyzerOptions options = value.HasValue ? new AnalyzerOptions { IsOnOpen = value.Value } : null; -// SetUpInitializedConfigScope(); -// -// testSubject.ExecuteAnalysis(FilePath, default, default, options, default); -// -// analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => -// a.shouldFetchServerIssues == expected), -// Arg.Any()); -// } -// -// [TestMethod] -// public void ExecuteAnalysis_ForCFamily_PassesCompilationDatabaseAsExtraProperties() -// { -// const string filePath = @"C:\file\path\myclass.cpp"; -// const string compilationDatabasePath = @"C:\file\path\compilation_database.json"; -// var compilationDatabaseHandle = CreateCompilationDatabaseHandle(compilationDatabasePath); -// SetUpCompilationDatabaseLocator(filePath, compilationDatabaseHandle); -// SetUpInitializedConfigScope(); -// -// testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default); -// -// analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => -// a.extraProperties != null -// && a.extraProperties["sonar.cfamily.compile-commands"] == compilationDatabasePath), -// Arg.Any()); -// compilationDatabaseHandle.Received().Dispose(); -// } -// -// [TestMethod] -// public void ExecuteAnalysis_ForCFamily_AnalysisThrows_CompilationDatabaaseDisposed() -// { -// const string filePath = @"C:\file\path\myclass.cpp"; -// const string compilationDatabasePath = @"C:\file\path\compilation_database.json"; -// var compilationDatabaseHandle = CreateCompilationDatabaseHandle(compilationDatabasePath); -// SetUpCompilationDatabaseLocator(filePath, compilationDatabaseHandle); -// SetUpInitializedConfigScope(); -// analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(); -// -// testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default); -// -// compilationDatabaseHandle.Received().Dispose(); -// } -// -// [TestMethod] -// public void ExecuteAnalysis_ForCFamily_WithoutCompilationDatabase_PassesEmptyStringAsExtraProperty() -// { -// const string filePath = @"C:\file\path\myclass.cpp"; -// SetUpCompilationDatabaseLocator(filePath, null); -// SetUpInitializedConfigScope(); -// -// testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default); -// -// analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => -// a.extraProperties != null -// && a.extraProperties.ContainsKey("sonar.cfamily.compile-commands") -// && a.extraProperties["sonar.cfamily.compile-commands"] == ""), -// Arg.Any()); -// } -// -// [TestMethod] -// public void ExecuteAnalysis_PassesCorrectCancellationTokenToAnalysisService() -// { -// var cancellationTokenSource = new CancellationTokenSource(); -// SetUpInitializedConfigScope(); -// -// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, cancellationTokenSource.Token); -// -// analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Any(), -// cancellationTokenSource.Token); -// } -// -// [TestMethod] -// public void ExecuteAnalysis_AnalysisServiceSucceeds_ExitsWithoutFinishingAnalysis() -// { -// SetUpInitializedConfigScope(); -// analysisService.AnalyzeFilesAndTrackAsync(default, default).ReturnsForAnyArgs(new AnalyzeFilesResponse(new HashSet(), [])); -// -// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); -// -// notifier.DidNotReceiveWithAnyArgs().AnalysisNotReady(default); -// notifier.DidNotReceiveWithAnyArgs().AnalysisFailed(default(Exception)); -// notifier.DidNotReceiveWithAnyArgs().AnalysisFailed(default(string)); -// notifier.DidNotReceiveWithAnyArgs().AnalysisFinished(default, default); -// } -// -// [TestMethod] -// public void ExecuteAnalysis_AnalysisServiceFailsForFile_NotifyFailed() -// { -// SetUpInitializedConfigScope(); -// analysisService.AnalyzeFilesAndTrackAsync(default, default).ReturnsForAnyArgs(new AnalyzeFilesResponse(new HashSet { new(@"C:\file\path") }, [])); -// -// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); -// -// notifier.Received().AnalysisFailed(SLCoreStrings.AnalysisFailedReason); -// } -// -// [TestMethod] -// public void ExecuteAnalysis_AnalysisServiceCancelled_NotifyCancel() -// { -// SetUpInitializedConfigScope(); -// var operationCanceledException = new OperationCanceledException(); -// analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(operationCanceledException); -// -// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); -// -// notifier.Received().AnalysisCancelled(); -// } -// -// [TestMethod] -// public void ExecuteAnalysis_AnalysisServiceThrows_NotifyFailed() -// { -// SetUpInitializedConfigScope(); -// var exception = new Exception(); -// analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(exception); -// -// testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); -// -// notifier.Received().AnalysisFailed(exception); -// } -// -// private void SetUpServiceProvider(bool result = true) => -// slCoreServiceProvider.TryGetTransientService(out Arg.Any()) -// .Returns(info => -// { -// info[0] = analysisService; -// return result; -// }); -// -// private void SetUpInitializedConfigScope() => -// activeConfigScopeTracker.Current.Returns(new ConfigurationScope(ConfigScopeId, IsReadyForAnalysis: true)); -// -// private void SetUpCurrentTimeProvider(DateTimeOffset nowTime) => -// currentTimeProvider.Now.Returns(nowTime); -// -// private static ICompilationDatabaseHandle CreateCompilationDatabaseHandle(string compilationDatabasePath) -// { -// var handle = Substitute.For(); -// handle.FilePath.Returns(compilationDatabasePath); -// return handle; -// } -// -// private void SetUpCompilationDatabaseLocator(string filePath, ICompilationDatabaseHandle handle) => -// compilationDatabaseLocator.GetOrNull(filePath).Returns(handle); -// } +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using NSubstitute.ExceptionExtensions; +using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.Analysis; +using SonarLint.VisualStudio.Core.CFamily; +using SonarLint.VisualStudio.Core.ConfigurationScope; +using SonarLint.VisualStudio.Core.SystemAbstractions; +using SonarLint.VisualStudio.SLCore.Analysis; +using SonarLint.VisualStudio.SLCore.Common.Models; +using SonarLint.VisualStudio.SLCore.Core; +using SonarLint.VisualStudio.SLCore.Service.Analysis; + +namespace SonarLint.VisualStudio.SLCore.UnitTests.Analysis; + +[TestClass] +public class SLCoreAnalyzerTests +{ + private const string ConfigScopeId = "ConfigScopeId"; + private const string FilePath = @"C:\file\path"; + private Guid analysisId; + private ISLCoreServiceProvider slCoreServiceProvider; + private IAnalysisSLCoreService analysisService; + private IActiveConfigScopeTracker activeConfigScopeTracker; + private IAnalysisStatusNotifierFactory analysisStatusNotifierFactory; + private ICurrentTimeProvider currentTimeProvider; + private IAggregatingCompilationDatabaseProvider compilationDatabaseLocator; + private IAnalysisStatusNotifier notifier; + private ILogger logger; + private SLCoreAnalyzer testSubject; + + [TestInitialize] + public void TestInitialize() + { + analysisId = Guid.NewGuid(); + slCoreServiceProvider = Substitute.For(); + analysisService = Substitute.For(); + SetUpServiceProvider(); + activeConfigScopeTracker = Substitute.For(); + analysisStatusNotifierFactory = Substitute.For(); + notifier = Substitute.For(); + SetUpDefaultNotifier(); + currentTimeProvider = Substitute.For(); + compilationDatabaseLocator = Substitute.For(); + logger = new TestLogger(); + testSubject = new SLCoreAnalyzer(slCoreServiceProvider, + activeConfigScopeTracker, + analysisStatusNotifierFactory, + currentTimeProvider, + compilationDatabaseLocator, + logger); + + void SetUpDefaultNotifier() => analysisStatusNotifierFactory.Create(nameof(SLCoreAnalyzer), FilePath, analysisId).Returns(notifier); + } + + [TestMethod] + public void MefCtor_CheckIsExported() => + MefTestHelpers.CheckTypeCanBeImported( + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport()); + + [TestMethod] + public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); + + [TestMethod] + public void ExecuteAnalysis_CreatesNotifierAndStarts() + { + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); + + analysisStatusNotifierFactory.Received().Create(nameof(SLCoreAnalyzer), FilePath, analysisId); + notifier.Received().AnalysisStarted(); + } + + [TestMethod] + public void ExecuteAnalysis_ConfigScopeNotInitialized_NotifyNotReady() + { + activeConfigScopeTracker.Current.Returns((ConfigurationScope)null); + + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); + + _ = activeConfigScopeTracker.Received().Current; + analysisService.ReceivedCalls().Should().BeEmpty(); + notifier.Received().AnalysisNotReady(SLCoreStrings.ConfigScopeNotInitialized); + } + + [TestMethod] + public void ExecuteAnalysis_ConfigScopeNotReadyForAnalysis_NotifyNotReady() + { + activeConfigScopeTracker.Current.Returns(new ConfigurationScope(ConfigScopeId, IsReadyForAnalysis: false)); + + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); + + _ = activeConfigScopeTracker.Received().Current; + analysisService.ReceivedCalls().Should().BeEmpty(); + notifier.Received().AnalysisNotReady(SLCoreStrings.ConfigScopeNotInitialized); + } + + [TestMethod] + public void ExecuteAnalysis_ServiceProviderUnavailable_NotifyFailed() + { + SetUpServiceProvider(false); + SetUpInitializedConfigScope(); + + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); + + slCoreServiceProvider.Received().TryGetTransientService(out Arg.Any()); + analysisService.ReceivedCalls().Should().BeEmpty(); + notifier.Received().AnalysisFailed(SLCoreStrings.ServiceProviderNotInitialized); + } + + [TestMethod] + public void ExecuteAnalysis_PassesCorrectArgumentsToAnalysisService() + { + var expectedTimeStamp = DateTimeOffset.Now; + SetUpCurrentTimeProvider(expectedTimeStamp); + SetUpInitializedConfigScope(); + + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); + + analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => + a.analysisId == analysisId + && a.configurationScopeId == ConfigScopeId + && a.filesToAnalyze.Single() == new FileUri(FilePath) + && a.extraProperties.Count == 0 + && a.startTime == expectedTimeStamp.ToUnixTimeMilliseconds()), + Arg.Any()); + } + + [DataTestMethod] + [DataRow(null, false)] + [DataRow(false, false)] + [DataRow(true, true)] + public void ExecuteAnalysis_ShouldFetchServerIssues_PassesCorrectValueToAnalysisService(bool? value, bool expected) + { + IAnalyzerOptions options = value.HasValue ? new AnalyzerOptions { IsOnOpen = value.Value } : null; + SetUpInitializedConfigScope(); + + testSubject.ExecuteAnalysis(FilePath, default, default, options, default); + + analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => + a.shouldFetchServerIssues == expected), + Arg.Any()); + } + + [TestMethod] + public void ExecuteAnalysis_ForCFamily_PassesCompilationDatabaseAsExtraProperties() + { + const string filePath = @"C:\file\path\myclass.cpp"; + const string compilationDatabasePath = @"C:\file\path\compilation_database.json"; + var compilationDatabaseHandle = CreateCompilationDatabaseHandle(compilationDatabasePath); + SetUpCompilationDatabaseLocator(filePath, compilationDatabaseHandle); + SetUpInitializedConfigScope(); + + testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default); + + analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => + a.extraProperties != null + && a.extraProperties["sonar.cfamily.compile-commands"] == compilationDatabasePath), + Arg.Any()); + compilationDatabaseHandle.Received().Dispose(); + } + + [TestMethod] + public void ExecuteAnalysis_ForCFamily_AnalysisThrows_CompilationDatabaaseDisposed() + { + const string filePath = @"C:\file\path\myclass.cpp"; + const string compilationDatabasePath = @"C:\file\path\compilation_database.json"; + var compilationDatabaseHandle = CreateCompilationDatabaseHandle(compilationDatabasePath); + SetUpCompilationDatabaseLocator(filePath, compilationDatabaseHandle); + SetUpInitializedConfigScope(); + analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(); + + testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default); + + compilationDatabaseHandle.Received().Dispose(); + } + + [TestMethod] + public void ExecuteAnalysis_ForCFamily_WithoutCompilationDatabase_PassesEmptyStringAsExtraProperty() + { + const string filePath = @"C:\file\path\myclass.cpp"; + SetUpCompilationDatabaseLocator(filePath, null); + SetUpInitializedConfigScope(); + + testSubject.ExecuteAnalysis(filePath, analysisId, [AnalysisLanguage.CFamily], default, default); + + analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Is(a => + a.extraProperties != null + && a.extraProperties.ContainsKey("sonar.cfamily.compile-commands") + && a.extraProperties["sonar.cfamily.compile-commands"] == ""), + Arg.Any()); + } + + [TestMethod] + public void ExecuteAnalysis_PassesCorrectCancellationTokenToAnalysisService() + { + var cancellationTokenSource = new CancellationTokenSource(); + SetUpInitializedConfigScope(); + + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, cancellationTokenSource.Token); + + analysisService.Received().AnalyzeFilesAndTrackAsync(Arg.Any(), + cancellationTokenSource.Token); + } + + [TestMethod] + public void ExecuteAnalysis_AnalysisServiceSucceeds_ExitsByFinishingAnalysis() + { + SetUpInitializedConfigScope(); + analysisService.AnalyzeFilesAndTrackAsync(default, default).ReturnsForAnyArgs(new AnalyzeFilesResponse(new HashSet(), [])); + + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); + + notifier.DidNotReceiveWithAnyArgs().AnalysisNotReady(default); + notifier.DidNotReceiveWithAnyArgs().AnalysisFailed(default(Exception)); + notifier.DidNotReceiveWithAnyArgs().AnalysisFailed(default(string)); + notifier.Received().AnalysisFinished(Arg.Is(x => x > TimeSpan.Zero)); + } + + [TestMethod] + public void ExecuteAnalysis_AnalysisServiceFailsForFile_NotifyFailed() + { + SetUpInitializedConfigScope(); + analysisService.AnalyzeFilesAndTrackAsync(default, default).ReturnsForAnyArgs(new AnalyzeFilesResponse(new HashSet { new(@"C:\file\path") }, [])); + + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); + + notifier.Received().AnalysisFailed(SLCoreStrings.AnalysisFailedReason); + } + + [TestMethod] + public void ExecuteAnalysis_AnalysisServiceCancelled_NotifyCancel() + { + SetUpInitializedConfigScope(); + var operationCanceledException = new OperationCanceledException(); + analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(operationCanceledException); + + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); + + notifier.Received().AnalysisCancelled(); + } + + [TestMethod] + public void ExecuteAnalysis_AnalysisServiceThrows_NotifyFailed() + { + SetUpInitializedConfigScope(); + var exception = new Exception(); + analysisService.AnalyzeFilesAndTrackAsync(default, default).ThrowsAsyncForAnyArgs(exception); + + testSubject.ExecuteAnalysis(FilePath, analysisId, default, default, default); + + notifier.Received().AnalysisFailed(exception); + } + + private void SetUpServiceProvider(bool result = true) => + slCoreServiceProvider.TryGetTransientService(out Arg.Any()) + .Returns(info => + { + info[0] = analysisService; + return result; + }); + + private void SetUpInitializedConfigScope() => + activeConfigScopeTracker.Current.Returns(new ConfigurationScope(ConfigScopeId, IsReadyForAnalysis: true)); + + private void SetUpCurrentTimeProvider(DateTimeOffset nowTime) => + currentTimeProvider.Now.Returns(nowTime); + + private static ICompilationDatabaseHandle CreateCompilationDatabaseHandle(string compilationDatabasePath) + { + var handle = Substitute.For(); + handle.FilePath.Returns(compilationDatabasePath); + return handle; + } + + private void SetUpCompilationDatabaseLocator(string filePath, ICompilationDatabaseHandle handle) => + compilationDatabaseLocator.GetOrNull(filePath).Returns(handle); +} From b3c5338f0193dfd7865e6a4baf783ffc69108823 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Tue, 17 Dec 2024 09:58:52 +0100 Subject: [PATCH 4/4] Upd --- .../Analysis/HotspotsPublisherTests.cs | 4 + .../Analysis/IssuePublisherTests.cs | 4 + .../Analysis/AnalysisStatusNotifierTests.cs | 589 +++++++++--------- src/SLCore/Analysis/SLCoreAnalyzer.cs | 2 +- 4 files changed, 308 insertions(+), 291 deletions(-) diff --git a/src/Core.UnitTests/Analysis/HotspotsPublisherTests.cs b/src/Core.UnitTests/Analysis/HotspotsPublisherTests.cs index 346672f84f..bcbe605bb4 100644 --- a/src/Core.UnitTests/Analysis/HotspotsPublisherTests.cs +++ b/src/Core.UnitTests/Analysis/HotspotsPublisherTests.cs @@ -46,6 +46,10 @@ public void TestInitialize() testSubject = new HotspotPublisher(issueConsumerStorage); } + [TestMethod] + public void FindingsType_ReturnsCorrectValue() => + testSubject.FindingsType.Should().Be(CoreStrings.FindingType_Hotspot); + [TestMethod] public void PublishHotspots_NoConsumerInStorage_DoesNothing() { diff --git a/src/Core.UnitTests/Analysis/IssuePublisherTests.cs b/src/Core.UnitTests/Analysis/IssuePublisherTests.cs index 237d187890..953b1999fa 100644 --- a/src/Core.UnitTests/Analysis/IssuePublisherTests.cs +++ b/src/Core.UnitTests/Analysis/IssuePublisherTests.cs @@ -46,6 +46,10 @@ public void TestInitialize() testSubject = new IssuePublisher(issueConsumerStorage); } + [TestMethod] + public void FindingsType_ReturnsCorrectValue() => + testSubject.FindingsType.Should().Be(CoreStrings.FindingType_Issue); + [TestMethod] public void PublishIssues_NoConsumerInStorage_DoesNothing() { diff --git a/src/Integration.Vsix.UnitTests/Analysis/AnalysisStatusNotifierTests.cs b/src/Integration.Vsix.UnitTests/Analysis/AnalysisStatusNotifierTests.cs index acc7092d1e..4f43cd601f 100644 --- a/src/Integration.Vsix.UnitTests/Analysis/AnalysisStatusNotifierTests.cs +++ b/src/Integration.Vsix.UnitTests/Analysis/AnalysisStatusNotifierTests.cs @@ -1,290 +1,299 @@ -// /* -// * SonarLint for Visual Studio -// * Copyright (C) 2016-2024 SonarSource SA -// * mailto:info AT sonarsource DOT com -// * -// * This program is free software; you can redistribute it and/or -// * modify it under the terms of the GNU Lesser General Public -// * License as published by the Free Software Foundation; either -// * version 3 of the License, or (at your option) any later version. -// * -// * This program is distributed in the hope that it will be useful, -// * but WITHOUT ANY WARRANTY; without even the implied warranty of -// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// * Lesser General Public License for more details. -// * -// * You should have received a copy of the GNU Lesser General Public License -// * along with this program; if not, write to the Free Software Foundation, -// * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -// */ -// -// using System; -// using System.Collections.Generic; -// using FluentAssertions; -// using Microsoft.VisualStudio.TestTools.UnitTesting; -// using Moq; -// using SonarLint.VisualStudio.Core; -// using SonarLint.VisualStudio.Integration.Vsix.Analysis; -// using SonarLint.VisualStudio.Integration.Vsix.Helpers; -// using SonarLint.VisualStudio.TestInfrastructure; -// -// namespace SonarLint.VisualStudio.Integration.UnitTests.Analysis -// { -// [TestClass] -// public class AnalysisStatusNotifierTests -// { -// [TestMethod] -// [DataRow("foo-started.cpp", "foo-started.cpp")] -// [DataRow("c:\\test\\foo-started.cpp", "foo-started.cpp")] -// [DataRow("..\\test\\foo-started.cpp", "foo-started.cpp")] -// public void AnalysisStarted_DisplayMessageAndStartSpinner(string filePath, string expectedNotifiedFileName) -// { -// var statusBarMock = new Mock(); -// -// var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); -// testSubject.AnalysisStarted(); -// -// var expectedMessage = string.Format(AnalysisStrings.Notifier_AnalysisStarted, expectedNotifiedFileName); -// VerifyStatusBarMessageAndIcon(statusBarMock, expectedMessage, true); -// } -// -// [TestMethod] -// public void AnalysisStarted_LogToOutputWindow() -// { -// const string analyzerName = "some analyzer"; -// const string filePath = "c:\\test\\foo-started.cpp"; -// var logger = new TestLogger(); -// var analysisId = Guid.NewGuid(); -// -// var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); -// testSubject.AnalysisStarted(); -// -// var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisStarted, filePath, analysisId); -// logger.AssertPartialOutputStringExists(analyzerName); -// logger.AssertPartialOutputStringExists(expectedMessage); -// logger.OutputStrings.Count.Should().Be(1); -// } -// -// [TestMethod] -// [DataRow("foo-finished.cpp", "foo-finished.cpp")] -// [DataRow("c:\\test\\foo-finished.cpp", "foo-finished.cpp")] -// [DataRow("..\\test\\foo-finished.cpp", "foo-finished.cpp")] -// public void AnalysisFinished_DisplayMessageAndStopSpinner(string filePath, string expectedNotifiedFileName) -// { -// var statusBarMock = new Mock(); -// -// var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); -// testSubject.AnalysisFinished(1, TimeSpan.Zero); -// -// var expectedMessage = string.Format(AnalysisStrings.Notifier_AnalysisFinished, expectedNotifiedFileName); -// -// VerifyStatusBarMessageAndIcon(statusBarMock, expectedMessage, false); -// } -// -// [TestMethod] -// public void AnalysisFinished_LogToOutputWindow() -// { -// const string analyzerName = "some analyzer"; -// const string filePath = "c:\\test\\foo-started.cpp"; -// var logger = new TestLogger(); -// var analysisId = Guid.NewGuid(); -// -// var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); -// -// testSubject.AnalysisFinished(123, TimeSpan.FromSeconds(6.54321)); -// -// var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisComplete, filePath, analysisId, 6.543); -// logger.AssertPartialOutputStringExists(expectedMessage); -// -// expectedMessage = string.Format($"Found {123} issue(s) for {filePath}"); -// logger.AssertPartialOutputStringExists(expectedMessage); -// -// logger.AssertPartialOutputStringExists(analyzerName); -// -// logger.OutputStrings.Count.Should().Be(2); -// } -// -// [TestMethod] -// [DataRow("foo-timedout.cpp")] -// [DataRow("c:\\test\\foo-timedout.cpp")] -// [DataRow("..\\test\\foo-timedout.cpp")] -// public void AnalysisCancelled_RemoveMessageAndStopSpinner(string filePath) -// { -// var statusBarMock = new Mock(); -// -// var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); -// -// testSubject.AnalysisCancelled(); -// -// VerifyStatusBarMessageAndIcon(statusBarMock, "", false); -// } -// -// [TestMethod] -// public void AnalysisCancelled_LogToOutputWindow() -// { -// const string analyzerName = "some analyzer"; -// const string filePath = "c:\\test\\foo-started.cpp"; -// var logger = new TestLogger(); -// var analysisId = Guid.NewGuid(); -// -// var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); -// -// testSubject.AnalysisCancelled(); -// -// var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisAborted, filePath, analysisId); -// logger.AssertPartialOutputStringExists(analyzerName); -// logger.AssertPartialOutputStringExists(expectedMessage); -// logger.OutputStrings.Count.Should().Be(1); -// } -// -// [TestMethod] -// [DataRow("foo-timedout.cpp")] -// [DataRow("c:\\test\\foo-timedout.cpp")] -// [DataRow("..\\test\\foo-timedout.cpp")] -// public void AnalysisNotReady_RemoveMessageAndStopSpinner(string filePath) -// { -// var statusBarMock = new Mock(); -// -// var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); -// -// testSubject.AnalysisNotReady("some reason"); -// -// VerifyStatusBarMessageAndIcon(statusBarMock, "", false); -// } -// -// [TestMethod] -// public void AnalysisNotReady_LogToOutputWindow() -// { -// const string analyzerName = "some analyzer"; -// const string filePath = "c:\\test\\foo-started.cpp"; -// const string reason = "some reason"; -// var logger = new TestLogger(); -// var analysisId = Guid.NewGuid(); -// -// var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); -// -// testSubject.AnalysisNotReady(reason); -// -// var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisNotReady, filePath, analysisId, reason); -// logger.AssertPartialOutputStringExists(analyzerName); -// logger.AssertPartialOutputStringExists(expectedMessage); -// logger.OutputStrings.Count.Should().Be(1); -// } -// -// -// [TestMethod] -// [DataRow("foo-failed.cpp", "foo-failed.cpp")] -// [DataRow("c:\\test\\foo-failed.cpp", "foo-failed.cpp")] -// [DataRow("..\\test\\foo-failed.cpp", "foo-failed.cpp")] -// public void AnalysisFailed_DisplayMessageAndStopSpinner(string filePath, string expectedNotifiedFileName) -// { -// var statusBarMock = new Mock(); -// -// var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); -// -// testSubject.AnalysisFailed(new NullReferenceException("test message")); -// -// var expectedMessage = string.Format(AnalysisStrings.Notifier_AnalysisFailed, expectedNotifiedFileName); -// -// VerifyStatusBarMessageAndIcon(statusBarMock, expectedMessage, false); -// } -// -// -// -// [TestMethod] -// [DataRow("foo-failed.cpp", "foo-failed.cpp")] -// [DataRow("c:\\test\\foo-failed.cpp", "foo-failed.cpp")] -// [DataRow("..\\test\\foo-failed.cpp", "foo-failed.cpp")] -// public void AnalysisFailed_FailureMessage_DisplayMessageAndStopSpinner(string filePath, string expectedNotifiedFileName) -// { -// var statusBarMock = new Mock(); -// -// var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); -// -// testSubject.AnalysisFailed("test message"); -// -// var expectedMessage = string.Format(AnalysisStrings.Notifier_AnalysisFailed, expectedNotifiedFileName); -// -// VerifyStatusBarMessageAndIcon(statusBarMock, expectedMessage, false); -// } -// -// [TestMethod] -// public void AnalysisFailed_LogToOutputWindow() -// { -// const string analyzerName = "some analyzer"; -// const string filePath = "c:\\test\\foo-started.cpp"; -// var logger = new TestLogger(); -// var analysisId = Guid.NewGuid(); -// -// var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); -// -// var exception = new NullReferenceException("test message"); -// testSubject.AnalysisFailed(exception); -// -// var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisFailed, filePath, analysisId, exception); -// logger.AssertPartialOutputStringExists(analyzerName); -// logger.AssertPartialOutputStringExists(expectedMessage); -// logger.OutputStrings.Count.Should().Be(1); -// } -// -// [TestMethod] -// public void AnalysisFailed_FailureMessage_LogToOutputWindow() -// { -// const string analyzerName = "some analyzer"; -// const string filePath = "c:\\test\\foo-started.cpp"; -// var logger = new TestLogger(); -// var analysisId = Guid.NewGuid(); -// -// var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); -// -// testSubject.AnalysisFailed("test message"); -// -// var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisFailed, filePath, analysisId, "test message"); -// logger.AssertPartialOutputStringExists(analyzerName); -// logger.AssertPartialOutputStringExists(expectedMessage); -// logger.OutputStrings.Count.Should().Be(1); -// } -// -// [TestMethod] -// public void AnalysisFailed_AggregateException_LogToOutputWindow() -// { -// const string analyzerName = "some analyzer"; -// const string filePath = "c:\\test\\foo-started.cpp"; -// var logger = new TestLogger(); -// -// var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), analyzerName, logger: logger); -// -// var exception = new AggregateException( -// new List -// { -// new ArgumentNullException("this is a test1"), -// new NotImplementedException("this is a test2") -// }); -// -// testSubject.AnalysisFailed(exception); -// -// logger.AssertPartialOutputStringExists(analyzerName); -// logger.AssertPartialOutputStringExists("this is a test1"); -// logger.AssertPartialOutputStringExists("this is a test2"); -// logger.OutputStrings.Count.Should().Be(1); -// } -// -// private void VerifyStatusBarMessageAndIcon(Mock statusBarMock, string expectedMessage, bool isSpinnerOn) -// { -// statusBarMock.Verify(x=> x.Notify(expectedMessage, isSpinnerOn), Times.Once); -// statusBarMock.VerifyNoOtherCalls(); -// } -// -// private AnalysisStatusNotifier CreateTestSubject(string filePath, -// Guid? analysisId, -// string analyzerName = "analyzer", -// IStatusBarNotifier statusBarNotifier = null, -// ILogger logger = null) -// { -// statusBarNotifier ??= Mock.Of(); -// logger ??= new TestLogger(); -// -// return new AnalysisStatusNotifier(analyzerName, filePath, analysisId, statusBarNotifier, logger); -// } -// } -// } +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Integration.Vsix.Analysis; +using SonarLint.VisualStudio.Integration.Vsix.Helpers; + +namespace SonarLint.VisualStudio.Integration.UnitTests.Analysis +{ + [TestClass] + public class AnalysisStatusNotifierTests + { + [TestMethod] + [DataRow("foo-started.cpp", "foo-started.cpp")] + [DataRow("c:\\test\\foo-started.cpp", "foo-started.cpp")] + [DataRow("..\\test\\foo-started.cpp", "foo-started.cpp")] + public void AnalysisStarted_DisplayMessageAndStartSpinner(string filePath, string expectedNotifiedFileName) + { + var statusBarMock = new Mock(); + + var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); + testSubject.AnalysisStarted(); + + var expectedMessage = string.Format(AnalysisStrings.Notifier_AnalysisStarted, expectedNotifiedFileName); + VerifyStatusBarMessageAndIcon(statusBarMock, expectedMessage, true); + } + + [TestMethod] + public void AnalysisStarted_LogToOutputWindow() + { + const string analyzerName = "some analyzer"; + const string filePath = "c:\\test\\foo-started.cpp"; + var logger = new TestLogger(); + var analysisId = Guid.NewGuid(); + + var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); + testSubject.AnalysisStarted(); + + var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisStarted, filePath, analysisId); + logger.AssertPartialOutputStringExists(analyzerName); + logger.AssertPartialOutputStringExists(expectedMessage); + logger.OutputStrings.Count.Should().Be(1); + } + + [DataRow(true)] + [DataRow(false)] + [DataTestMethod] + public void AnalysisProgressed_LogToOutputWindow(bool isIntermediate) + { + const string analyzerName = "some analyzer"; + const string filePath = "c:\\test\\foo-started.cpp"; + var logger = new TestLogger(); + var analysisId = Guid.NewGuid(); + + var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); + testSubject.AnalysisProgressed(123, "finding", isIntermediate); + + var expectedMessage = string.Format(AnalysisStrings.MSG_FoundIssues, 123, "finding", filePath, analysisId, !isIntermediate); + logger.AssertPartialOutputStringExists(expectedMessage); + logger.AssertPartialOutputStringExists(analyzerName); + } + + [TestMethod] + [DataRow("foo-finished.cpp", "foo-finished.cpp")] + [DataRow("c:\\test\\foo-finished.cpp", "foo-finished.cpp")] + [DataRow("..\\test\\foo-finished.cpp", "foo-finished.cpp")] + public void AnalysisFinished_DisplayMessageAndStopSpinner(string filePath, string expectedNotifiedFileName) + { + var statusBarMock = new Mock(); + var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); + + testSubject.AnalysisFinished(TimeSpan.FromSeconds(3)); + + var expectedMessage = string.Format(AnalysisStrings.Notifier_AnalysisFinished, expectedNotifiedFileName); + + VerifyStatusBarMessageAndIcon(statusBarMock, expectedMessage, false); + } + + [TestMethod] + public void AnalysisFinished_LogToOutputWindow() + { + const string analyzerName = "some analyzer"; + const string filePath = "c:\\test\\foo-started.cpp"; + var logger = new TestLogger(); + var analysisId = Guid.NewGuid(); + + var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); + + testSubject.AnalysisFinished(TimeSpan.FromSeconds(6.54321)); + + var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisComplete, filePath, analysisId, 6.543); + logger.AssertPartialOutputStringExists(expectedMessage); + + logger.AssertPartialOutputStringExists(analyzerName); + + logger.OutputStrings.Count.Should().Be(1); + } + + [TestMethod] + [DataRow("foo-timedout.cpp")] + [DataRow("c:\\test\\foo-timedout.cpp")] + [DataRow("..\\test\\foo-timedout.cpp")] + public void AnalysisCancelled_RemoveMessageAndStopSpinner(string filePath) + { + var statusBarMock = new Mock(); + + var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); + + testSubject.AnalysisCancelled(); + + VerifyStatusBarMessageAndIcon(statusBarMock, "", false); + } + + [TestMethod] + public void AnalysisCancelled_LogToOutputWindow() + { + const string analyzerName = "some analyzer"; + const string filePath = "c:\\test\\foo-started.cpp"; + var logger = new TestLogger(); + var analysisId = Guid.NewGuid(); + + var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); + + testSubject.AnalysisCancelled(); + + var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisAborted, filePath, analysisId); + logger.AssertPartialOutputStringExists(analyzerName); + logger.AssertPartialOutputStringExists(expectedMessage); + logger.OutputStrings.Count.Should().Be(1); + } + + [TestMethod] + [DataRow("foo-timedout.cpp")] + [DataRow("c:\\test\\foo-timedout.cpp")] + [DataRow("..\\test\\foo-timedout.cpp")] + public void AnalysisNotReady_RemoveMessageAndStopSpinner(string filePath) + { + var statusBarMock = new Mock(); + + var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); + + testSubject.AnalysisNotReady("some reason"); + + VerifyStatusBarMessageAndIcon(statusBarMock, "", false); + } + + [TestMethod] + public void AnalysisNotReady_LogToOutputWindow() + { + const string analyzerName = "some analyzer"; + const string filePath = "c:\\test\\foo-started.cpp"; + const string reason = "some reason"; + var logger = new TestLogger(); + var analysisId = Guid.NewGuid(); + + var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); + + testSubject.AnalysisNotReady(reason); + + var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisNotReady, filePath, analysisId, reason); + logger.AssertPartialOutputStringExists(analyzerName); + logger.AssertPartialOutputStringExists(expectedMessage); + logger.OutputStrings.Count.Should().Be(1); + } + + + [TestMethod] + [DataRow("foo-failed.cpp", "foo-failed.cpp")] + [DataRow("c:\\test\\foo-failed.cpp", "foo-failed.cpp")] + [DataRow("..\\test\\foo-failed.cpp", "foo-failed.cpp")] + public void AnalysisFailed_DisplayMessageAndStopSpinner(string filePath, string expectedNotifiedFileName) + { + var statusBarMock = new Mock(); + + var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); + + testSubject.AnalysisFailed(new NullReferenceException("test message")); + + var expectedMessage = string.Format(AnalysisStrings.Notifier_AnalysisFailed, expectedNotifiedFileName); + + VerifyStatusBarMessageAndIcon(statusBarMock, expectedMessage, false); + } + + + + [TestMethod] + [DataRow("foo-failed.cpp", "foo-failed.cpp")] + [DataRow("c:\\test\\foo-failed.cpp", "foo-failed.cpp")] + [DataRow("..\\test\\foo-failed.cpp", "foo-failed.cpp")] + public void AnalysisFailed_FailureMessage_DisplayMessageAndStopSpinner(string filePath, string expectedNotifiedFileName) + { + var statusBarMock = new Mock(); + + var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), statusBarNotifier: statusBarMock.Object); + + testSubject.AnalysisFailed("test message"); + + var expectedMessage = string.Format(AnalysisStrings.Notifier_AnalysisFailed, expectedNotifiedFileName); + + VerifyStatusBarMessageAndIcon(statusBarMock, expectedMessage, false); + } + + [TestMethod] + public void AnalysisFailed_LogToOutputWindow() + { + const string analyzerName = "some analyzer"; + const string filePath = "c:\\test\\foo-started.cpp"; + var logger = new TestLogger(); + var analysisId = Guid.NewGuid(); + + var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); + + var exception = new NullReferenceException("test message"); + testSubject.AnalysisFailed(exception); + + var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisFailed, filePath, analysisId, exception); + logger.AssertPartialOutputStringExists(analyzerName); + logger.AssertPartialOutputStringExists(expectedMessage); + logger.OutputStrings.Count.Should().Be(1); + } + + [TestMethod] + public void AnalysisFailed_FailureMessage_LogToOutputWindow() + { + const string analyzerName = "some analyzer"; + const string filePath = "c:\\test\\foo-started.cpp"; + var logger = new TestLogger(); + var analysisId = Guid.NewGuid(); + + var testSubject = CreateTestSubject(filePath, analysisId, analyzerName, logger: logger); + + testSubject.AnalysisFailed("test message"); + + var expectedMessage = string.Format(AnalysisStrings.MSG_AnalysisFailed, filePath, analysisId, "test message"); + logger.AssertPartialOutputStringExists(analyzerName); + logger.AssertPartialOutputStringExists(expectedMessage); + logger.OutputStrings.Count.Should().Be(1); + } + + [TestMethod] + public void AnalysisFailed_AggregateException_LogToOutputWindow() + { + const string analyzerName = "some analyzer"; + const string filePath = "c:\\test\\foo-started.cpp"; + var logger = new TestLogger(); + + var testSubject = CreateTestSubject(filePath, Guid.NewGuid(), analyzerName, logger: logger); + + var exception = new AggregateException( + new List + { + new ArgumentNullException("this is a test1"), + new NotImplementedException("this is a test2") + }); + + testSubject.AnalysisFailed(exception); + + logger.AssertPartialOutputStringExists(analyzerName); + logger.AssertPartialOutputStringExists("this is a test1"); + logger.AssertPartialOutputStringExists("this is a test2"); + logger.OutputStrings.Count.Should().Be(1); + } + + private void VerifyStatusBarMessageAndIcon(Mock statusBarMock, string expectedMessage, bool isSpinnerOn) + { + statusBarMock.Verify(x=> x.Notify(expectedMessage, isSpinnerOn), Times.Once); + statusBarMock.VerifyNoOtherCalls(); + } + + private AnalysisStatusNotifier CreateTestSubject(string filePath, + Guid? analysisId, + string analyzerName = "analyzer", + IStatusBarNotifier statusBarNotifier = null, + ILogger logger = null) + { + statusBarNotifier ??= Mock.Of(); + logger ??= new TestLogger(); + + return new AnalysisStatusNotifier(analyzerName, filePath, analysisId, statusBarNotifier, logger); + } + } +} diff --git a/src/SLCore/Analysis/SLCoreAnalyzer.cs b/src/SLCore/Analysis/SLCoreAnalyzer.cs index fb19f412f1..7077ae6013 100644 --- a/src/SLCore/Analysis/SLCoreAnalyzer.cs +++ b/src/SLCore/Analysis/SLCoreAnalyzer.cs @@ -104,7 +104,7 @@ private async Task ExecuteAnalysisInternalAsync( Stopwatch stopwatch = Stopwatch.StartNew(); - var (failedAnalysisFiles, issues) = await analysisService.AnalyzeFilesAndTrackAsync( + var (failedAnalysisFiles, _) = await analysisService.AnalyzeFilesAndTrackAsync( new AnalyzeFilesAndTrackParams( configScopeId, analysisId,