diff --git a/.cirrus.yaml b/.cirrus.yaml index 34afcbae8..009046fd3 100644 --- a/.cirrus.yaml +++ b/.cirrus.yaml @@ -5,6 +5,7 @@ env: CIRRUS_SHELL: bash USERPROFILE: C:\sonar-ci # Fixes error MSB3073 and path too long issue with restored packages TMP_DIR: C:\sonar-ci\temp + MSVC: C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.40.33807\bin\Hostx64\x64\cl.exe # Required for CFamily integration tests ec2_instance_definition: &INSTANCE_DEFINITION region: eu-central-1 diff --git a/src/SLCore.IntegrationTests/FileAnalysisTestsRunner.cs b/src/SLCore.IntegrationTests/FileAnalysisTestsRunner.cs index e46c1af79..bcc1e2df1 100644 --- a/src/SLCore.IntegrationTests/FileAnalysisTestsRunner.cs +++ b/src/SLCore.IntegrationTests/FileAnalysisTestsRunner.cs @@ -41,6 +41,7 @@ internal sealed class FileAnalysisTestsRunner : IDisposable internal static readonly JavaScriptIssuesFile JavaScriptIssues = new(); internal static readonly OneIssueRuleWithParamFile OneIssueRuleWithParam = new(); internal static readonly TypeScriptIssuesFile TypeScriptIssues = new(); + internal static readonly CFamilyIssuesFile CFamilyIssues = new(); internal static readonly CssIssuesFile CssIssues = new(); internal static readonly VueIssuesFile VueIssues = new(); internal static readonly SecretsIssuesFile SecretsIssues = new(); @@ -79,8 +80,11 @@ public void SetRuleConfiguration(Dictionary rul rulesCoreService.UpdateStandaloneRulesConfiguration(new UpdateStandaloneRulesConfigurationParams(ruleConfig)); } - public async Task>> RunFileAnalysis(ITestingFile testingFile, string configScope, - bool sendContent = false) + public async Task>> RunFileAnalysis( + ITestingFile testingFile, + string configScope, + bool sendContent = false, + Dictionary extraProperties = null) { try { @@ -97,7 +101,7 @@ public async Task>> RunFileAnalysis(ITe await ConcurrencyTestHelper.WaitForTaskWithTimeout(analysisReadyCompletionSource.Task); - await RunSlCoreFileAnalysis(configScope, testingFile.GetFullPath(), analysisId); + await RunSlCoreFileAnalysis(configScope, testingFile.GetFullPath(), analysisId, extraProperties); await ConcurrencyTestHelper.WaitForTaskWithTimeout(analysisRaisedIssues.Task); return analysisRaisedIssues.Task.Result.issuesByFileUri; @@ -141,13 +145,15 @@ private void SetUpAnalysisListener( }); } - private async Task RunSlCoreFileAnalysis(string configScopeId, string fileToAnalyzeAbsolutePath, Guid analysisId) + private async Task RunSlCoreFileAnalysis(string configScopeId, string fileToAnalyzeAbsolutePath, Guid analysisId, Dictionary extraProperties = null) { + extraProperties ??= []; + slCoreTestRunner.SLCoreServiceProvider.TryGetTransientService(out IAnalysisSLCoreService analysisService).Should().BeTrue(); var (failedAnalysisFiles, _) = await analysisService.AnalyzeFilesAndTrackAsync( new AnalyzeFilesAndTrackParams(configScopeId, analysisId, - [new FileUri(fileToAnalyzeAbsolutePath)], [], false, + [new FileUri(fileToAnalyzeAbsolutePath)], extraProperties, false, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()), CancellationToken.None); failedAnalysisFiles.Should().BeEmpty(); } @@ -215,6 +221,19 @@ internal class TypeScriptIssuesFile : ITestingFile ]; } +internal class CFamilyIssuesFile : ITestingFile +{ + public string RelativePath => @"Resources\CFamilyIssues.cpp"; + + public List ExpectedIssues => + [ + new("cpp:S1135", new TextRangeDto(7, 4, 7, 29), CleanCodeAttribute.COMPLETE, 0), + new("cpp:S1481", new TextRangeDto(10, 9, 10, 10), CleanCodeAttribute.CLEAR, 0), + new("cpp:S5350", new TextRangeDto(10, 4, 10, 17), CleanCodeAttribute.CLEAR, 0), + new("cpp:S4962", new TextRangeDto(10, 13, 10, 17), CleanCodeAttribute.CONVENTIONAL, 0), + ]; +} + internal class CssIssuesFile : ITestingFile { public string RelativePath => @"Resources\CssIssues.css"; diff --git a/src/SLCore.IntegrationTests/Resources/CFamilyIssues.cpp b/src/SLCore.IntegrationTests/Resources/CFamilyIssues.cpp new file mode 100644 index 000000000..66672f845 --- /dev/null +++ b/src/SLCore.IntegrationTests/Resources/CFamilyIssues.cpp @@ -0,0 +1,13 @@ +#include + +using namespace std; + +int main() +{ + // TODO: This is an issue + cout << "Hello CMake." << endl; + + int* a = NULL; // Some more issues + + return 0; +} diff --git a/src/SLCore.IntegrationTests/SLCore.IntegrationTests.csproj b/src/SLCore.IntegrationTests/SLCore.IntegrationTests.csproj index ab5587b0b..56631bcd2 100644 --- a/src/SLCore.IntegrationTests/SLCore.IntegrationTests.csproj +++ b/src/SLCore.IntegrationTests/SLCore.IntegrationTests.csproj @@ -53,6 +53,9 @@ PreserveNewest + + PreserveNewest + diff --git a/src/SLCore.IntegrationTests/SimpleAnalysisTests.cs b/src/SLCore.IntegrationTests/SimpleAnalysisTests.cs index 89ed8aa24..b0448c0d8 100644 --- a/src/SLCore.IntegrationTests/SimpleAnalysisTests.cs +++ b/src/SLCore.IntegrationTests/SimpleAnalysisTests.cs @@ -18,8 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using System.IO; using SonarLint.VisualStudio.SLCore.Common.Models; -using SonarLint.VisualStudio.SLCore.Listener.Analysis.Models; namespace SonarLint.VisualStudio.SLCore.IntegrationTests; @@ -66,6 +66,14 @@ public Task DefaultRuleConfig_ContentFromDisk_TypeScriptAnalysisProducesExpected public Task DefaultRuleConfig_ContentFromRpc_TypeScriptAnalysisProducesExpectedIssues() => DefaultRuleConfig_AnalysisProducesExpectedIssuesInFile(FileAnalysisTestsRunner.TypeScriptIssues, true); + [TestMethod] + public Task DefaultRuleConfig_ContentFromDisk_CFamilyAnalysisProducesExpectedIssues() + => DefaultRuleConfig_AnalysisProducesExpectedIssuesInFile(FileAnalysisTestsRunner.CFamilyIssues, false, GenerateTestCompilationDatabase()); + + [TestMethod] + public Task DefaultRuleConfig_ContentFromRpc_CFamilyAnalysisProducesExpectedIssues() + => DefaultRuleConfig_AnalysisProducesExpectedIssuesInFile(FileAnalysisTestsRunner.CFamilyIssues, true, GenerateTestCompilationDatabase()); + [TestMethod] public Task DefaultRuleConfig_ContentFromDisk_CssAnalysisProducesExpectedIssues() => DefaultRuleConfig_AnalysisProducesExpectedIssuesInFile(FileAnalysisTestsRunner.CssIssues, false); @@ -82,13 +90,48 @@ public Task DefaultRuleConfig_ContentFromDisk_CssAnalysisInVueProducesExpectedIs public Task DefaultRuleConfig_ContentFromRpc_CssAnalysisInVyeProducesExpectedIssues() => DefaultRuleConfig_AnalysisProducesExpectedIssuesInFile(FileAnalysisTestsRunner.VueIssues, true); - private async Task DefaultRuleConfig_AnalysisProducesExpectedIssuesInFile(ITestingFile testingFile, bool sendContent) + private async Task DefaultRuleConfig_AnalysisProducesExpectedIssuesInFile(ITestingFile testingFile, bool sendContent, Dictionary extraProperties = null) { - var issuesByFileUri = await sharedFileAnalysisTestsRunner.RunFileAnalysis(testingFile, TestContext.TestName, sendContent: sendContent); + var issuesByFileUri = await sharedFileAnalysisTestsRunner.RunFileAnalysis(testingFile, TestContext.TestName, sendContent: sendContent, extraProperties: extraProperties); issuesByFileUri.Should().HaveCount(1); var receivedIssues = issuesByFileUri[new FileUri(testingFile.GetFullPath())]; var receivedTestIssues = receivedIssues.Select(x => new TestIssue(x.ruleKey, x.textRange, x.severityMode.Right?.cleanCodeAttribute, x.flows.Count)); receivedTestIssues.Should().BeEquivalentTo(testingFile.ExpectedIssues); } + + private static Dictionary GenerateTestCompilationDatabase() + { + /* The CFamily analysis apart from the source code file requires also the compilation database file. + The compilation database file must contain the absolute path to the source code file the compilation database json file and the compiler path. + For the compiler we use the MSVC which is set as an environment variable. Make sure the environment variable is set to point to the compiler path + (the absolute path to cl.exe). */ + var compilerPath = NormalizePath(Environment.GetEnvironmentVariable("MSVC")); + var cFamilyIssuesFileAbsolutePath = NormalizePath(FileAnalysisTestsRunner.CFamilyIssues.GetFullPath()); + var analysisDirectory = NormalizePath(Path.GetDirectoryName(cFamilyIssuesFileAbsolutePath)); + var jsonContent = $$""" + [ + { + "directory": "{{analysisDirectory}}", + "command": "\"{{compilerPath}}\" /nologo /TP /DWIN32 /D_WINDOWS /W3 /GR /EHsc /MDd /Ob0 /Od /RTC1 -std:c++20 -ZI /FoCFamilyIssues.cpp.obj /FS -c {{cFamilyIssuesFileAbsolutePath}}", + "file": "{{cFamilyIssuesFileAbsolutePath}}" + } + ] + """; + var tempCompilationDatabase = Path.ChangeExtension(Path.GetTempFileName(), ".json"); + File.WriteAllText(tempCompilationDatabase, jsonContent); + + var compilationDatabase = new Dictionary + { + { "sonar.cfamily.compile-commands", tempCompilationDatabase } + }; + return compilationDatabase; + } + + private static string NormalizePath(string path) + { + var singleDirectorySeparator = Path.DirectorySeparatorChar.ToString(); + var doubleDirectorySeparator = singleDirectorySeparator + singleDirectorySeparator; + return path?.Replace(singleDirectorySeparator, doubleDirectorySeparator); + } }