Skip to content

Commit

Permalink
SLVS-1660 Create integration tests for CFamily (#5933)
Browse files Browse the repository at this point in the history
  • Loading branch information
vnaskos-sonar authored Jan 10, 2025
1 parent ac3e2ca commit 99bec60
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 8 deletions.
1 change: 1 addition & 0 deletions .cirrus.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 24 additions & 5 deletions src/SLCore.IntegrationTests/FileAnalysisTestsRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -79,8 +80,11 @@ public void SetRuleConfiguration(Dictionary<string, StandaloneRuleConfigDto> rul
rulesCoreService.UpdateStandaloneRulesConfiguration(new UpdateStandaloneRulesConfigurationParams(ruleConfig));
}

public async Task<Dictionary<FileUri, List<RaisedIssueDto>>> RunFileAnalysis(ITestingFile testingFile, string configScope,
bool sendContent = false)
public async Task<Dictionary<FileUri, List<RaisedIssueDto>>> RunFileAnalysis(
ITestingFile testingFile,
string configScope,
bool sendContent = false,
Dictionary<string, string> extraProperties = null)
{
try
{
Expand All @@ -97,7 +101,7 @@ public async Task<Dictionary<FileUri, List<RaisedIssueDto>>> 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;
Expand Down Expand Up @@ -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<string, string> 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();
}
Expand Down Expand Up @@ -215,6 +221,19 @@ internal class TypeScriptIssuesFile : ITestingFile
];
}

internal class CFamilyIssuesFile : ITestingFile
{
public string RelativePath => @"Resources\CFamilyIssues.cpp";

public List<TestIssue> 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";
Expand Down
13 changes: 13 additions & 0 deletions src/SLCore.IntegrationTests/Resources/CFamilyIssues.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include <iostream>

using namespace std;

int main()
{
// TODO: This is an issue
cout << "Hello CMake." << endl;

int* a = NULL; // Some more issues

return 0;
}
3 changes: 3 additions & 0 deletions src/SLCore.IntegrationTests/SLCore.IntegrationTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@
<None Update="Resources\VueIssues.vue">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\CFamilyIssues.cpp">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<ItemGroup>
Expand Down
49 changes: 46 additions & 3 deletions src/SLCore.IntegrationTests/SimpleAnalysisTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand All @@ -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<string, string> 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<string, string> 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<string, string>
{
{ "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);
}
}

0 comments on commit 99bec60

Please sign in to comment.