Repository: SonarSource/sonar-scanner-msbuild
Pull request #2387: SCAN4NET-334 Extract ProjectKindTest
Author: login: pavel-mikula-sonarsource
Created: 2025-03-20T15:12:29Z | Merged: 2025-03-25T09:41:12Z

[SCAN4NET-334](https://sonarsource.atlassian.net/browse/SCAN4NET-334)

Move ITs into another file, without any modifications

[SCAN4NET-334]: https://sonarsource.atlassian.net/browse/SCAN4NET-334?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ


Diff:
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/BaseDirTest.java b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/BaseDirTest.java
index f85512f5ca..0c1f754863 100644
--- a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/BaseDirTest.java
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/BaseDirTest.java
@@ -48,7 +48,6 @@ class BaseDirTest {
 
   @Test
   void testCSharpSharedFileWithOneProjectWithoutProjectBaseDir() throws IOException {
-    runAnalysis("CSharpSharedFileWithOneProject");
     var projectKey = "CSharpSharedFileWithOneProject";
     var projectDir = TestUtils.projectDir(basePath, projectKey);
     String token = TestUtils.getNewToken(ORCHESTRATOR);
@@ -163,15 +162,6 @@ private void runCSharpSharedFileWithOneProjectUsingProjectBaseDir(Function<Path,
       .isNotNull();
   }
 
-  private BuildResult runAnalysis(String folderName) throws IOException {
-    return runAnalysis(folderName, false);
-  }
-
-  private BuildResult runAnalysis(String folderName, Boolean useNuGet) throws IOException {
-    Path projectDir = TestUtils.projectDir(basePath, folderName);
-    return TestUtils.runAnalysis(projectDir, folderName, useNuGet);
-  }
-
   private BuildResult runAnalysisWithoutProjectBasedDir(Path projectDir) {
     String token = TestUtils.getNewToken(ORCHESTRATOR);
     String folderName = projectDir.getFileName().toString();
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ScannerMSBuildTest.java b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ScannerMSBuildTest.java
index b51770c436..025a4c5bd0 100644
--- a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ScannerMSBuildTest.java
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ScannerMSBuildTest.java
@@ -28,15 +28,11 @@
 import com.sonar.orchestrator.util.CommandExecutor;
 import java.io.IOException;
 import java.nio.file.Path;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.stream.Collectors;
-import org.apache.commons.io.FileUtils;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.api.io.TempDir;
-import org.sonarqube.ws.Ce;
 import org.sonarqube.ws.Components;
 import org.sonarqube.ws.Issues.Issue;
 import org.sonarqube.ws.client.WsClient;
@@ -44,9 +40,7 @@
 
 import static com.sonar.it.scanner.msbuild.sonarqube.ServerTests.ORCHESTRATOR;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.tuple;
 import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assumptions.assumeFalse;
 import static org.junit.jupiter.api.Assumptions.assumeTrue;
 
 @ExtendWith(ServerTests.class)
@@ -117,61 +111,6 @@ void excludeAssemblyAttribute() throws Exception {
     assertThat(result.getLogs()).doesNotContain("AssemblyAttributes.cs");
   }
 
-  @Test
-  void testXamlCompilation() throws IOException {
-    // We can't build with MSBuild 15
-    // error MSB4018: System.InvalidOperationException: This implementation is not part of the Windows Platform FIPS validated cryptographic algorithms.
-    // at System.Security.Cryptography.MD5CryptoServiceProvider..ctor()
-    assumeFalse(TestUtils.getMsBuildPath(ORCHESTRATOR).toString().contains("2017"));
-
-    var projectKey = "XamarinApplication";
-    BuildResult result = runAnalysis(projectKey, true);
-    assertTrue(result.isSuccess());
-
-    List<Issue> issues = TestUtils.projectIssues(ORCHESTRATOR, projectKey);
-    assertThat(issues)
-      .extracting(Issue::getRule, Issue::getComponent)
-      .containsExactlyInAnyOrder(
-        tuple(SONAR_RULES_PREFIX + "S927", "XamarinApplication:XamarinApplication.iOS/AppDelegate.cs"),
-        tuple(SONAR_RULES_PREFIX + "S927", "XamarinApplication:XamarinApplication.iOS/AppDelegate.cs"),
-        tuple(SONAR_RULES_PREFIX + "S1118", "XamarinApplication:XamarinApplication.iOS/Main.cs"),
-        tuple(SONAR_RULES_PREFIX + "S1186", "XamarinApplication:XamarinApplication.iOS/Main.cs"),
-        tuple(SONAR_RULES_PREFIX + "S1186", "XamarinApplication:XamarinApplication/App.xaml.cs"),
-        tuple(SONAR_RULES_PREFIX + "S1186", "XamarinApplication:XamarinApplication/App.xaml.cs"),
-        tuple(SONAR_RULES_PREFIX + "S1186", "XamarinApplication:XamarinApplication/App.xaml.cs"),
-        tuple(SONAR_RULES_PREFIX + "S1134", "XamarinApplication:XamarinApplication/MainPage.xaml.cs"),
-        tuple("external_roslyn:CS0618", "XamarinApplication:XamarinApplication.iOS/Main.cs"));
-
-    assertThat(TestUtils.getMeasureAsInteger("XamarinApplication", "lines", ORCHESTRATOR)).isEqualTo(149);
-    assertThat(TestUtils.getMeasureAsInteger("XamarinApplication", "ncloc", ORCHESTRATOR)).isEqualTo(93);
-    assertThat(TestUtils.getMeasureAsInteger("XamarinApplication", "files", ORCHESTRATOR)).isEqualTo(6);
-    assertThat(TestUtils.getMeasureAsInteger("XamarinApplication:XamarinApplication.iOS", "lines", ORCHESTRATOR)).isEqualTo(97);
-    assertThat(TestUtils.getMeasureAsInteger("XamarinApplication:XamarinApplication", "lines", ORCHESTRATOR)).isEqualTo(52);
-  }
-
-  @Test
-  void testRazorCompilationNet9WithoutSourceGenerators() throws IOException {
-    assumeTrue(TestUtils.getMsBuildPath(ORCHESTRATOR).toString().contains("2022")); // We can't build without MsBuild17
-    String projectName = "RazorWebApplication.net9.withoutSourceGenerators";
-    assertProjectFileContains(projectName, "<UseRazorSourceGenerator>false</UseRazorSourceGenerator>");
-    validateRazorProject(projectName);
-  }
-
-  @Test
-  void testRazorCompilationNet9WithSourceGenerators() throws IOException {
-    assumeTrue(TestUtils.getMsBuildPath(ORCHESTRATOR).toString().contains("2022")); // We can't build without MsBuild17
-    String projectName = "RazorWebApplication.net9.withSourceGenerators";
-    assertProjectFileContains(projectName, "<UseRazorSourceGenerator>true</UseRazorSourceGenerator>");
-    validateRazorProject(projectName);
-  }
-
-  @Test
-  void testCSharpAllFlat() throws IOException {
-    runAnalysis("CSharpAllFlat");
-
-    assertThat(getComponent("CSharpAllFlat:Common.cs")).isNotNull();
-  }
-
   @Test
   void testTargetUninstall() throws IOException {
     var projectKey = "testTargetUninstall";
@@ -183,81 +122,11 @@ void testTargetUninstall() throws IOException {
     assertThat(getComponent(projectKey + ":Common.cs")).isNotNull();
   }
 
-  @Test
-  void testCSharpSharedFiles() throws IOException {
-    runAnalysis("CSharpSharedFiles");
-
-    assertThat(getComponent("CSharpSharedFiles:Common.cs"))
-      .isNotNull();
-    String class1ComponentId = TestUtils.hasModules(ORCHESTRATOR)
-      ? "CSharpSharedFiles:CSharpSharedFiles:D8FEDBA2-D056-42FB-B146-5A409727B65D:Class1.cs"
-      : "CSharpSharedFiles:ClassLib1/Class1.cs";
-    assertThat(getComponent(class1ComponentId))
-      .isNotNull();
-    String class2ComponentId = TestUtils.hasModules(ORCHESTRATOR)
-      ? "CSharpSharedFiles:CSharpSharedFiles:72CD6ED2-481A-4828-BA15-8CD5F0472A77:Class2.cs"
-      : "CSharpSharedFiles:ClassLib2/Class2.cs";
-    assertThat(getComponent(class2ComponentId))
-      .isNotNull();
-  }
-
-  @Test
-  void testCSharpSharedProjectType() throws IOException {
-    runAnalysis("CSharpSharedProjectType");
-
-    assertThat(getComponent("CSharpSharedProjectType:SharedProject/TestEventInvoke.cs"))
-      .isNotNull();
-    String programComponentId1 = TestUtils.hasModules(ORCHESTRATOR)
-      ? "CSharpSharedProjectType:CSharpSharedProjectType:36F96F66-8136-46C0-B83B-EFAE05A8FFC1:Program.cs"
-      : "CSharpSharedProjectType:ConsoleApp1/Program.cs";
-    assertThat(getComponent(programComponentId1))
-      .isNotNull();
-    String programComponentId2 = TestUtils.hasModules(ORCHESTRATOR)
-      ? "CSharpSharedProjectType:CSharpSharedProjectType:F96D8AA1-BCE1-4655-8D65-08F2A5FAC15B:Program.cs"
-      : "CSharpSharedProjectType:ConsoleApp2/Program.cs";
-    assertThat(getComponent(programComponentId2))
-      .isNotNull();
-  }
-
-  @Test
-  void testCSharpFramework48() throws IOException {
-    var folderName = "CSharp.Framework.4.8";
-    assumeFalse(TestUtils.getMsBuildPath(ORCHESTRATOR).toString().contains("2017")); // We can't run .NET Core SDK under VS 2017 CI context
-    BuildResult buildResult = runAnalysis(folderName, true);
-
-    assertUIWarnings(buildResult);
-    List<Issue> issues = TestUtils.projectIssues(ORCHESTRATOR, folderName);
-    assertThat(issues).hasSize(2)
-      .extracting(Issue::getRule, Issue::getComponent)
-      .containsExactlyInAnyOrder(
-        tuple(SONAR_RULES_PREFIX + "S1134", folderName + ":Main/Common.cs"),
-        tuple(SONAR_RULES_PREFIX + "S2699", folderName + ":UTs/CommonTest.cs"));
-  }
-
-  @Test
-  void testCSharpSdk8() throws IOException {
-    validateCSharpSdk("CSharp.SDK.8");
-  }
-
-  @Test
-  void testScannerNet8NoAnalysisWarnings() throws IOException {
-    // dotnet sdk tests should run only on VS 2022
-    assumeTrue(TestUtils.getMsBuildPath(ORCHESTRATOR).toString().contains("2022"));
-
-    BuildResult buildResult = runAnalysis("CSharp.SDK.8");
-
-    assertThat(buildResult.getLogs()).doesNotContain("Failed to parse properties from the environment variable 'SONARQUBE_SCANNER_PARAMS'");
-    assertUIWarnings(buildResult);
-  }
-
-  @Test
-  void testCSharpSdkLatest() throws IOException {
-    validateCSharpSdk("CSharp.SDK.Latest");
-  }
-
   @Test
   void testProjectTypeDetectionWithWrongCasingReferenceName() throws IOException {
-    BuildResult buildResult = runAnalysis("DotnetProjectTypeDetection");
+    var projectKey = "DotnetProjectTypeDetection";
+    Path projectDir = TestUtils.projectDir(basePath, projectKey);
+    BuildResult buildResult = TestUtils.runAnalysis(projectDir, projectKey, false);
     assertThat(buildResult.getLogs()).contains("Found 1 MSBuild C# project: 1 TEST project.");
   }
 
@@ -283,55 +152,6 @@ void testDuplicateAnalyzersWithSameNameAreNotRemoved() throws IOException {
     assertThat(TestUtils.getMeasureAsInteger("DuplicateAnalyzerReferences", "files", ORCHESTRATOR)).isEqualTo(2);
   }
 
-  private void validateCSharpSdk(String folderName) throws IOException {
-    // dotnet sdk tests should run only on VS 2022
-    assumeTrue(TestUtils.getMsBuildPath(ORCHESTRATOR).toString().contains("2022"));
-
-    runAnalysis(folderName);
-
-    List<Issue> issues = TestUtils.projectIssues(ORCHESTRATOR, folderName);
-
-    assertThat(issues).hasSize(2)
-      .extracting(Issue::getRule, Issue::getComponent)
-      .containsExactlyInAnyOrder(
-        tuple(SONAR_RULES_PREFIX + "S1134", folderName + ":Main/Common.cs"),
-        tuple(SONAR_RULES_PREFIX + "S2699", folderName + ":UTs/CommonTest.cs"));
-    // The AspNetCoreMvc/Views/Home/Index.cshtml contains an external CS0219 issue
-    // which is currently not imported due to the fact that the generated code Index.cshtml.g.cs is in the object folder.
-  }
-
-  private void assertUIWarnings(BuildResult buildResult) {
-    // AnalysisWarningsSensor was implemented starting from analyzer version 8.39.0.47922 (https://github.com/SonarSource/sonar-dotnet-enterprise/commit/39baabb01799aa1945ac5c80d150f173e6ada45f)
-    // So it's available from SQ 9.9 onwards
-    if (ORCHESTRATOR.getServer().version().isGreaterThanOrEquals(9, 9)) {
-      var warnings = TestUtils.getAnalysisWarningsTask(ORCHESTRATOR, buildResult);
-      assertThat(warnings.getStatus()).isEqualTo(Ce.TaskStatus.SUCCESS);
-      var warningsList = warnings.getWarningsList();
-      assertThat(warningsList.stream().anyMatch(
-        // The warning is appended to the timestamp, we want to assert only the message
-        x -> x.endsWith("Multi-Language analysis is enabled. If this was not intended and you have issues such as hitting your LOC limit or analyzing unwanted files, please set " +
-          "\"/d:sonar.scanner.scanAll=false\" in the begin step.")
-      )).isTrue();
-      assertThat(warningsList.size()).isEqualTo(1);
-    }
-  }
-
-  private void assertProjectFileContains(String projectName, String textToLookFor) throws IOException {
-    Path projectPath = TestUtils.projectDir(basePath, projectName);
-    Path csProjPath = projectPath.resolve("RazorWebApplication\\RazorWebApplication.csproj");
-    String str = FileUtils.readFileToString(csProjPath.toFile(), "utf-8");
-    assertThat(str.indexOf(textToLookFor)).isPositive();
-  }
-
-  private BuildResult runAnalysis(String folderName) throws IOException {
-    return runAnalysis(folderName, false);
-  }
-
-  private BuildResult runAnalysis(String folderName, Boolean useNuGet) throws IOException {
-    Path projectDir = TestUtils.projectDir(basePath, folderName);
-    return TestUtils.runAnalysis(projectDir, folderName, useNuGet);
-  }
-
   private BuildResult runNetCoreBeginBuildAndEnd(Path projectDir) {
     String token = TestUtils.getNewToken(ORCHESTRATOR);
     String folderName = projectDir.getFileName().toString();
@@ -357,33 +177,6 @@ private BuildResult runNetCoreBeginBuildAndEnd(Path projectDir) {
     return TestUtils.executeEndStepAndDumpResults(ORCHESTRATOR, projectDir, folderName, token, ScannerClassifier.NET, Collections.emptyList(), Collections.emptyList());
   }
 
-  private void validateRazorProject(String projectKey) throws IOException {
-    ORCHESTRATOR.getServer().provisionProject(projectKey, projectKey);
-
-    if (TestUtils.getMsBuildPath(ORCHESTRATOR).toString().contains("2017")) {
-      return; // We can't build razor under VS 2017 CI context
-    }
-
-    Path projectDir = TestUtils.projectDir(basePath, projectKey);
-    String token = TestUtils.getNewToken(ORCHESTRATOR);
-
-    TestUtils.newScannerBegin(ORCHESTRATOR, projectKey, projectDir, token, ScannerClassifier.NET_FRAMEWORK).execute(ORCHESTRATOR);
-    TestUtils.runNuGet(ORCHESTRATOR, projectDir, false, "restore");
-    TestUtils.runDotnetCommand(projectDir, "build", "--no-incremental");
-    BuildResult result = TestUtils.executeEndStepAndDumpResults(ORCHESTRATOR, projectDir, projectKey, token);
-
-    assertTrue(result.isSuccess());
-
-    List<Issue> issues = TestUtils.projectIssues(ORCHESTRATOR, projectKey);
-    List<String> ruleKeys = issues.stream().map(Issue::getRule).collect(Collectors.toList());
-
-    assertThat(ruleKeys).containsAll(Arrays.asList(SONAR_RULES_PREFIX + "S1118", SONAR_RULES_PREFIX + "S1186"));
-
-    assertThat(TestUtils.getMeasureAsInteger(projectKey, "lines", ORCHESTRATOR)).isEqualTo(49);
-    assertThat(TestUtils.getMeasureAsInteger(projectKey, "ncloc", ORCHESTRATOR)).isEqualTo(39);
-    assertThat(TestUtils.getMeasureAsInteger(projectKey, "files", ORCHESTRATOR)).isEqualTo(2);
-  }
-
   private static Components.Component getComponent(String componentKey) {
     return newWsClient().components().show(new ShowRequest().setComponent(componentKey)).getComponent();
   }
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/SolutionKindTest.java b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/SolutionKindTest.java
new file mode 100644
index 0000000000..a96b97b9ee
--- /dev/null
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/SolutionKindTest.java
@@ -0,0 +1,264 @@
+/*
+ * SonarScanner for .NET
+ * Copyright (C) 2016-2025 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.
+ */
+package com.sonar.it.scanner.msbuild.sonarqube;
+
+import com.sonar.it.scanner.msbuild.utils.ScannerClassifier;
+import com.sonar.it.scanner.msbuild.utils.TestUtils;
+import com.sonar.orchestrator.build.BuildResult;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.io.FileUtils;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.io.TempDir;
+import org.sonarqube.ws.Ce;
+import org.sonarqube.ws.Components;
+import org.sonarqube.ws.Issues.Issue;
+import org.sonarqube.ws.client.WsClient;
+import org.sonarqube.ws.client.components.ShowRequest;
+
+import static com.sonar.it.scanner.msbuild.sonarqube.ServerTests.ORCHESTRATOR;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeFalse;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+@ExtendWith(ServerTests.class)
+class SolutionKindTest {
+  private static final String SONAR_RULES_PREFIX = "csharpsquid:";
+
+  @TempDir
+  public Path basePath;
+
+  @Test
+  void testXamlCompilation() throws IOException {
+    // We can't build with MSBuild 15
+    // error MSB4018: System.InvalidOperationException: This implementation is not part of the Windows Platform FIPS validated cryptographic algorithms.
+    // at System.Security.Cryptography.MD5CryptoServiceProvider..ctor()
+    assumeFalse(TestUtils.getMsBuildPath(ORCHESTRATOR).toString().contains("2017"));
+
+    var projectKey = "XamarinApplication";
+    BuildResult result = runAnalysis(projectKey, true);
+    assertTrue(result.isSuccess());
+
+    List<Issue> issues = TestUtils.projectIssues(ORCHESTRATOR, projectKey);
+    assertThat(issues)
+      .extracting(Issue::getRule, Issue::getComponent)
+      .containsExactlyInAnyOrder(
+        tuple(SONAR_RULES_PREFIX + "S927", "XamarinApplication:XamarinApplication.iOS/AppDelegate.cs"),
+        tuple(SONAR_RULES_PREFIX + "S927", "XamarinApplication:XamarinApplication.iOS/AppDelegate.cs"),
+        tuple(SONAR_RULES_PREFIX + "S1118", "XamarinApplication:XamarinApplication.iOS/Main.cs"),
+        tuple(SONAR_RULES_PREFIX + "S1186", "XamarinApplication:XamarinApplication.iOS/Main.cs"),
+        tuple(SONAR_RULES_PREFIX + "S1186", "XamarinApplication:XamarinApplication/App.xaml.cs"),
+        tuple(SONAR_RULES_PREFIX + "S1186", "XamarinApplication:XamarinApplication/App.xaml.cs"),
+        tuple(SONAR_RULES_PREFIX + "S1186", "XamarinApplication:XamarinApplication/App.xaml.cs"),
+        tuple(SONAR_RULES_PREFIX + "S1134", "XamarinApplication:XamarinApplication/MainPage.xaml.cs"),
+        tuple("external_roslyn:CS0618", "XamarinApplication:XamarinApplication.iOS/Main.cs"));
+
+    assertThat(TestUtils.getMeasureAsInteger("XamarinApplication", "lines", ORCHESTRATOR)).isEqualTo(149);
+    assertThat(TestUtils.getMeasureAsInteger("XamarinApplication", "ncloc", ORCHESTRATOR)).isEqualTo(93);
+    assertThat(TestUtils.getMeasureAsInteger("XamarinApplication", "files", ORCHESTRATOR)).isEqualTo(6);
+    assertThat(TestUtils.getMeasureAsInteger("XamarinApplication:XamarinApplication.iOS", "lines", ORCHESTRATOR)).isEqualTo(97);
+    assertThat(TestUtils.getMeasureAsInteger("XamarinApplication:XamarinApplication", "lines", ORCHESTRATOR)).isEqualTo(52);
+  }
+
+  @Test
+  void testRazorCompilationNet9WithoutSourceGenerators() throws IOException {
+    assumeTrue(TestUtils.getMsBuildPath(ORCHESTRATOR).toString().contains("2022")); // We can't build without MsBuild17
+    String projectName = "RazorWebApplication.net9.withoutSourceGenerators";
+    assertProjectFileContains(projectName, "<UseRazorSourceGenerator>false</UseRazorSourceGenerator>");
+    validateRazorProject(projectName);
+  }
+
+  @Test
+  void testRazorCompilationNet9WithSourceGenerators() throws IOException {
+    assumeTrue(TestUtils.getMsBuildPath(ORCHESTRATOR).toString().contains("2022")); // We can't build without MsBuild17
+    String projectName = "RazorWebApplication.net9.withSourceGenerators";
+    assertProjectFileContains(projectName, "<UseRazorSourceGenerator>true</UseRazorSourceGenerator>");
+    validateRazorProject(projectName);
+  }
+
+  @Test
+  void testCSharpAllFlat() throws IOException {
+    runAnalysis("CSharpAllFlat");
+
+    assertThat(getComponent("CSharpAllFlat:Common.cs")).isNotNull();
+  }
+
+  @Test
+  void testCSharpSharedFiles() throws IOException {
+    runAnalysis("CSharpSharedFiles");
+
+    assertThat(getComponent("CSharpSharedFiles:Common.cs"))
+      .isNotNull();
+    String class1ComponentId = TestUtils.hasModules(ORCHESTRATOR)
+      ? "CSharpSharedFiles:CSharpSharedFiles:D8FEDBA2-D056-42FB-B146-5A409727B65D:Class1.cs"
+      : "CSharpSharedFiles:ClassLib1/Class1.cs";
+    assertThat(getComponent(class1ComponentId))
+      .isNotNull();
+    String class2ComponentId = TestUtils.hasModules(ORCHESTRATOR)
+      ? "CSharpSharedFiles:CSharpSharedFiles:72CD6ED2-481A-4828-BA15-8CD5F0472A77:Class2.cs"
+      : "CSharpSharedFiles:ClassLib2/Class2.cs";
+    assertThat(getComponent(class2ComponentId))
+      .isNotNull();
+  }
+
+  @Test
+  void testCSharpSharedProjectType() throws IOException {
+    runAnalysis("CSharpSharedProjectType");
+
+    assertThat(getComponent("CSharpSharedProjectType:SharedProject/TestEventInvoke.cs"))
+      .isNotNull();
+    String programComponentId1 = TestUtils.hasModules(ORCHESTRATOR)
+      ? "CSharpSharedProjectType:CSharpSharedProjectType:36F96F66-8136-46C0-B83B-EFAE05A8FFC1:Program.cs"
+      : "CSharpSharedProjectType:ConsoleApp1/Program.cs";
+    assertThat(getComponent(programComponentId1))
+      .isNotNull();
+    String programComponentId2 = TestUtils.hasModules(ORCHESTRATOR)
+      ? "CSharpSharedProjectType:CSharpSharedProjectType:F96D8AA1-BCE1-4655-8D65-08F2A5FAC15B:Program.cs"
+      : "CSharpSharedProjectType:ConsoleApp2/Program.cs";
+    assertThat(getComponent(programComponentId2))
+      .isNotNull();
+  }
+
+  @Test
+  void testCSharpFramework48() throws IOException {
+    var folderName = "CSharp.Framework.4.8";
+    assumeFalse(TestUtils.getMsBuildPath(ORCHESTRATOR).toString().contains("2017")); // We can't run .NET Core SDK under VS 2017 CI context
+    BuildResult buildResult = runAnalysis(folderName, true);
+
+    assertUIWarnings(buildResult);
+    List<Issue> issues = TestUtils.projectIssues(ORCHESTRATOR, folderName);
+    assertThat(issues).hasSize(2)
+      .extracting(Issue::getRule, Issue::getComponent)
+      .containsExactlyInAnyOrder(
+        tuple(SONAR_RULES_PREFIX + "S1134", folderName + ":Main/Common.cs"),
+        tuple(SONAR_RULES_PREFIX + "S2699", folderName + ":UTs/CommonTest.cs"));
+  }
+
+  @Test
+  void testCSharpSdk8() throws IOException {
+    validateCSharpSdk("CSharp.SDK.8");
+  }
+
+  @Test
+  void testScannerNet8NoAnalysisWarnings() throws IOException {
+    // dotnet sdk tests should run only on VS 2022
+    assumeTrue(TestUtils.getMsBuildPath(ORCHESTRATOR).toString().contains("2022"));
+
+    BuildResult buildResult = runAnalysis("CSharp.SDK.8");
+
+    assertThat(buildResult.getLogs()).doesNotContain("Failed to parse properties from the environment variable 'SONARQUBE_SCANNER_PARAMS'");
+    assertUIWarnings(buildResult);
+  }
+
+  @Test
+  void testCSharpSdkLatest() throws IOException {
+    validateCSharpSdk("CSharp.SDK.Latest");
+  }
+
+  private void validateCSharpSdk(String folderName) throws IOException {
+    // dotnet sdk tests should run only on VS 2022
+    assumeTrue(TestUtils.getMsBuildPath(ORCHESTRATOR).toString().contains("2022"));
+
+    runAnalysis(folderName);
+
+    List<Issue> issues = TestUtils.projectIssues(ORCHESTRATOR, folderName);
+
+    assertThat(issues).hasSize(2)
+      .extracting(Issue::getRule, Issue::getComponent)
+      .containsExactlyInAnyOrder(
+        tuple(SONAR_RULES_PREFIX + "S1134", folderName + ":Main/Common.cs"),
+        tuple(SONAR_RULES_PREFIX + "S2699", folderName + ":UTs/CommonTest.cs"));
+    // The AspNetCoreMvc/Views/Home/Index.cshtml contains an external CS0219 issue
+    // which is currently not imported due to the fact that the generated code Index.cshtml.g.cs is in the object folder.
+  }
+
+  private void assertUIWarnings(BuildResult buildResult) {
+    // AnalysisWarningsSensor was implemented starting from analyzer version 8.39.0.47922 (https://github.com/SonarSource/sonar-dotnet-enterprise/commit/39baabb01799aa1945ac5c80d150f173e6ada45f)
+    // So it's available from SQ 9.9 onwards
+    if (ORCHESTRATOR.getServer().version().isGreaterThanOrEquals(9, 9)) {
+      var warnings = TestUtils.getAnalysisWarningsTask(ORCHESTRATOR, buildResult);
+      assertThat(warnings.getStatus()).isEqualTo(Ce.TaskStatus.SUCCESS);
+      var warningsList = warnings.getWarningsList();
+      assertThat(warningsList.stream().anyMatch(
+        // The warning is appended to the timestamp, we want to assert only the message
+        x -> x.endsWith("Multi-Language analysis is enabled. If this was not intended and you have issues such as hitting your LOC limit or analyzing unwanted files, please set " +
+          "\"/d:sonar.scanner.scanAll=false\" in the begin step.")
+      )).isTrue();
+      assertThat(warningsList.size()).isEqualTo(1);
+    }
+  }
+
+  private void assertProjectFileContains(String projectName, String textToLookFor) throws IOException {
+    Path projectPath = TestUtils.projectDir(basePath, projectName);
+    Path csProjPath = projectPath.resolve("RazorWebApplication\\RazorWebApplication.csproj");
+    String str = FileUtils.readFileToString(csProjPath.toFile(), "utf-8");
+    assertThat(str.indexOf(textToLookFor)).isPositive();
+  }
+
+  private BuildResult runAnalysis(String folderName) throws IOException {
+    return runAnalysis(folderName, false);
+  }
+
+  private BuildResult runAnalysis(String folderName, Boolean useNuGet) throws IOException {
+    Path projectDir = TestUtils.projectDir(basePath, folderName);
+    return TestUtils.runAnalysis(projectDir, folderName, useNuGet);
+  }
+
+  private void validateRazorProject(String projectKey) throws IOException {
+    ORCHESTRATOR.getServer().provisionProject(projectKey, projectKey);
+
+    if (TestUtils.getMsBuildPath(ORCHESTRATOR).toString().contains("2017")) {
+      return; // We can't build razor under VS 2017 CI context
+    }
+
+    Path projectDir = TestUtils.projectDir(basePath, projectKey);
+    String token = TestUtils.getNewToken(ORCHESTRATOR);
+
+    TestUtils.newScannerBegin(ORCHESTRATOR, projectKey, projectDir, token, ScannerClassifier.NET_FRAMEWORK).execute(ORCHESTRATOR);
+    TestUtils.runNuGet(ORCHESTRATOR, projectDir, false, "restore");
+    TestUtils.runDotnetCommand(projectDir, "build", "--no-incremental");
+    BuildResult result = TestUtils.executeEndStepAndDumpResults(ORCHESTRATOR, projectDir, projectKey, token);
+
+    assertTrue(result.isSuccess());
+
+    List<Issue> issues = TestUtils.projectIssues(ORCHESTRATOR, projectKey);
+    List<String> ruleKeys = issues.stream().map(Issue::getRule).collect(Collectors.toList());
+
+    assertThat(ruleKeys).containsAll(Arrays.asList(SONAR_RULES_PREFIX + "S1118", SONAR_RULES_PREFIX + "S1186"));
+
+    assertThat(TestUtils.getMeasureAsInteger(projectKey, "lines", ORCHESTRATOR)).isEqualTo(49);
+    assertThat(TestUtils.getMeasureAsInteger(projectKey, "ncloc", ORCHESTRATOR)).isEqualTo(39);
+    assertThat(TestUtils.getMeasureAsInteger(projectKey, "files", ORCHESTRATOR)).isEqualTo(2);
+  }
+
+  private static Components.Component getComponent(String componentKey) {
+    return newWsClient().components().show(new ShowRequest().setComponent(componentKey)).getComponent();
+  }
+
+  private static WsClient newWsClient() {
+    return TestUtils.newWsClient(ORCHESTRATOR);
+  }
+}


Reviews:
user: login: pavel-mikula-sonarsource
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T15:13:55Z
user: login: pavel-mikula-sonarsource
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T13:42:15Z
user: login: martin-strecker-sonarsource
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T08:15:26Z
user: login: pavel-mikula-sonarsource
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T09:20:46Z

Review comments:
diff_hunk: @@ -0,0 +1,263 @@
+/*
+ * SonarScanner for .NET
+ * Copyright (C) 2016-2025 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.
+ */
+package com.sonar.it.scanner.msbuild.sonarqube;
+
+import com.sonar.it.scanner.msbuild.utils.ScannerClassifier;
+import com.sonar.it.scanner.msbuild.utils.TestUtils;
+import com.sonar.orchestrator.build.BuildResult;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.io.FileUtils;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.io.TempDir;
+import org.sonarqube.ws.Ce;
+import org.sonarqube.ws.Components;
+import org.sonarqube.ws.Issues.Issue;
+import org.sonarqube.ws.client.WsClient;
+import org.sonarqube.ws.client.components.ShowRequest;
+
+import static com.sonar.it.scanner.msbuild.sonarqube.Tests.ORCHESTRATOR;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeFalse;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+@ExtendWith(Tests.class)
+class ProjectKindTest {
path: its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ProjectKindTest.java
user: login: pavel-mikula-sonarsource
body: I'm not very happy with this name. Let me know if you have a better idea.

It's meant to test things that differ by different project types, like SDK/non-SDK/xaml/razor/aspire/whatever. And "ProjectType" in scanner context means Main/Test code, so we should not use that.
created_at: 2025-03-20T15:13:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -48,7 +48,6 @@ class BaseDirTest {
 
   @Test
   void testCSharpSharedFileWithOneProjectWithoutProjectBaseDir() throws IOException {
-    runAnalysis("CSharpSharedFileWithOneProject");
path: its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/BaseDirTest.java
user: login: pavel-mikula-sonarsource
body: Redundant to the actions below. Probably copy/paste or rebase issue from previous PRs
created_at: 2025-03-24T13:42:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,263 @@
+/*
+ * SonarScanner for .NET
+ * Copyright (C) 2016-2025 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.
+ */
+package com.sonar.it.scanner.msbuild.sonarqube;
+
+import com.sonar.it.scanner.msbuild.utils.ScannerClassifier;
+import com.sonar.it.scanner.msbuild.utils.TestUtils;
+import com.sonar.orchestrator.build.BuildResult;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.io.FileUtils;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.io.TempDir;
+import org.sonarqube.ws.Ce;
+import org.sonarqube.ws.Components;
+import org.sonarqube.ws.Issues.Issue;
+import org.sonarqube.ws.client.WsClient;
+import org.sonarqube.ws.client.components.ShowRequest;
+
+import static com.sonar.it.scanner.msbuild.sonarqube.Tests.ORCHESTRATOR;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeFalse;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+@ExtendWith(Tests.class)
+class ProjectKindTest {
path: its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ProjectKindTest.java
user: login: martin-strecker-sonarsource
body: SolutionKindTest?

created_at: 2025-03-25T08:14:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,263 @@
+/*
+ * SonarScanner for .NET
+ * Copyright (C) 2016-2025 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.
+ */
+package com.sonar.it.scanner.msbuild.sonarqube;
+
+import com.sonar.it.scanner.msbuild.utils.ScannerClassifier;
+import com.sonar.it.scanner.msbuild.utils.TestUtils;
+import com.sonar.orchestrator.build.BuildResult;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.io.FileUtils;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.io.TempDir;
+import org.sonarqube.ws.Ce;
+import org.sonarqube.ws.Components;
+import org.sonarqube.ws.Issues.Issue;
+import org.sonarqube.ws.client.WsClient;
+import org.sonarqube.ws.client.components.ShowRequest;
+
+import static com.sonar.it.scanner.msbuild.sonarqube.Tests.ORCHESTRATOR;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeFalse;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+@ExtendWith(Tests.class)
+class ProjectKindTest {
path: its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ProjectKindTest.java
user: login: pavel-mikula-sonarsource
body: Sounds better
created_at: 2025-03-25T09:20:46Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-03-25T09:27:52Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=sonarscanner-msbuild&pullRequest=2387) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=sonarscanner-msbuild&pullRequest=2387&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=sonarscanner-msbuild&pullRequest=2387&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=sonarscanner-msbuild&pullRequest=2387&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=sonarscanner-msbuild&pullRequest=2387&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=sonarscanner-msbuild&pullRequest=2387&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=sonarscanner-msbuild&pullRequest=2387)


author_association: NONE