Repository: SonarSource/sonar-scanner-msbuild
Pull request #2416: SCAN4NET-298 Use test method name as projectKey
Author: login: pavel-mikula-sonarsource
Created: 2025-03-26T10:21:45Z | Merged: 2025-03-27T09:50:46Z

[SCAN4NET-298](https://sonarsource.atlassian.net/browse/SCAN4NET-298)



[SCAN4NET-298]: https://sonarsource.atlassian.net/browse/SCAN4NET-298?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ


Diff:
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarcloud/CloudUtils.java b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarcloud/CloudUtils.java
index 390b151113..58058ea959 100644
--- a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarcloud/CloudUtils.java
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarcloud/CloudUtils.java
@@ -89,6 +89,7 @@ public static void waitForTaskProcessing(String logs) {
     if (matcher.find()) {
       var uri = URI.create(matcher.group(1));
       var client = HttpClient.newHttpClient();
+      var request = HttpRequest.newBuilder(uri).header("Authorization", "Bearer " + System.getenv("SONARCLOUD_PROJECT_TOKEN")).build();
 
       await()
         .pollInterval(Duration.ofSeconds(5))
@@ -96,7 +97,6 @@ public static void waitForTaskProcessing(String logs) {
         .until(() -> {
           try {
             LOG.info("Pooling for task status using {}", uri);
-            var request = HttpRequest.newBuilder(uri).header("Authorization", "Bearer " + System.getenv("SONARCLOUD_PROJECT_TOKEN")).build();
             var response = client.send(request, HttpResponse.BodyHandlers.ofString());
             return response.statusCode() == 200 && response.body().contains("\"status\":\"SUCCESS\"");
           } catch (HttpException ex) {
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarcloud/IncrementalPRAnalysisTest.java b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarcloud/IncrementalPRAnalysisTest.java
index 1c77bfb69d..6b4adf7bc5 100644
--- a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarcloud/IncrementalPRAnalysisTest.java
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarcloud/IncrementalPRAnalysisTest.java
@@ -20,6 +20,7 @@
 package com.sonar.it.scanner.msbuild.sonarcloud;
 
 import com.sonar.it.scanner.msbuild.utils.AnalysisContext;
+import com.sonar.it.scanner.msbuild.utils.ContextExtension;
 import com.sonar.it.scanner.msbuild.utils.Property;
 import com.sonar.it.scanner.msbuild.utils.TestUtils;
 import java.io.BufferedWriter;
@@ -36,10 +37,10 @@
 
 import static org.assertj.core.api.Assertions.assertThat;
 
-@ExtendWith(CloudTests.class)
+@ExtendWith({CloudTests.class, ContextExtension.class})
 class IncrementalPRAnalysisTest {
   private final static Logger LOG = LoggerFactory.getLogger(IncrementalPRAnalysisTest.class);
-  private final static String SONARCLOUD_PROJECT_KEY = "team-lang-dotnet_incremental-pr-analysis";
+  private final static String SONARCLOUD_PROJECT_KEY = "team-lang-dotnet_incremental-pr-analysis";  // ToDo: SCAN4NET-320 will remove this in favor of the dynamic context.projectKey
   private final static String PROJECT_NAME = "IncrementalPRAnalysis";
   private final static Property[] prArguments = {
     new Property("sonar.pullrequest.base", "master"),
@@ -62,7 +63,7 @@ void master_emptyCache() throws IOException {
 
   @Test
   void prWithoutChanges_producesUnchangedFilesWithAllFiles() throws IOException {
-    var context = AnalysisContext.forCloud(SONARCLOUD_PROJECT_KEY, basePath, PROJECT_NAME);
+    var context = AnalysisContext.forCloud(basePath, PROJECT_NAME);
     context.runAnalysis();  // Initial build - master.
 
     for (var property : prArguments) {
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarcloud/JreProvisioningTest.java b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarcloud/JreProvisioningTest.java
index 720cf5f82f..1a1582104a 100644
--- a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarcloud/JreProvisioningTest.java
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarcloud/JreProvisioningTest.java
@@ -19,6 +19,7 @@
  */
 package com.sonar.it.scanner.msbuild.sonarcloud;
 
+import com.sonar.it.scanner.msbuild.utils.ContextExtension;
 import com.sonar.it.scanner.msbuild.utils.Property;
 import com.sonar.it.scanner.msbuild.utils.ScannerClassifier;
 import com.sonar.it.scanner.msbuild.utils.ScannerCommand;
@@ -34,10 +35,10 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 
-@ExtendWith(CloudTests.class)
+@ExtendWith({CloudTests.class, ContextExtension.class})
 class JreProvisioningTest {
   private static final Logger LOG = LoggerFactory.getLogger(JreProvisioningTest.class);
-  private static final String SONARCLOUD_PROJECT_KEY = "team-lang-dotnet_jre-provisioning";
+  private static final String SONARCLOUD_PROJECT_KEY = "team-lang-dotnet_jre-provisioning"; // ToDo: SCAN4NET-320 will remove this in favor of the dynamic context.projectKey
   private static final String PROJECT_NAME = "JreProvisioning";
   private static final Property activateProvisioning = new Property("sonar.scanner.skipJreProvisioning", null); // Default ScannerCommand behavior turns it off
 
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarcloud/RegionTest.java b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarcloud/RegionTest.java
index cf9f10413b..d361a3cc34 100644
--- a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarcloud/RegionTest.java
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarcloud/RegionTest.java
@@ -19,6 +19,7 @@
  */
 package com.sonar.it.scanner.msbuild.sonarcloud;
 
+import com.sonar.it.scanner.msbuild.utils.ContextExtension;
 import com.sonar.it.scanner.msbuild.utils.ScannerClassifier;
 import com.sonar.it.scanner.msbuild.utils.ScannerCommand;
 import com.sonar.it.scanner.msbuild.utils.TestUtils;
@@ -33,10 +34,10 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 
-@ExtendWith(CloudTests.class)
+@ExtendWith({CloudTests.class, ContextExtension.class})
 public class RegionTest {
   private static final Logger LOG = LoggerFactory.getLogger(RegionTest.class);
-  private static final String SONARCLOUD_PROJECT_KEY = "team-lang-dotnet_region-parameter";
+  private static final String SONARCLOUD_PROJECT_KEY = "team-lang-dotnet_region-parameter"; // ToDo: SCAN4NET-320 will remove this in favor of the dynamic context.projectKey
   private static final String PROJECT_NAME = "ProjectUnderTest";
 
   @TempDir
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 e2809f5ff9..69baba811c 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
@@ -19,6 +19,7 @@
  */
 package com.sonar.it.scanner.msbuild.sonarqube;
 
+import com.sonar.it.scanner.msbuild.utils.ContextExtension;
 import com.sonar.it.scanner.msbuild.utils.ScannerClassifier;
 import com.sonar.it.scanner.msbuild.utils.TestUtils;
 import com.sonar.orchestrator.build.BuildResult;
@@ -39,7 +40,7 @@
 import static org.assertj.core.api.Assertions.tuple;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-@ExtendWith(ServerTests.class)
+@ExtendWith({ServerTests.class, ContextExtension.class})
 class BaseDirTest {
   private static final String SONAR_RULES_PREFIX = "csharpsquid:";
 
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/CodeCoverageTest.java b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/CodeCoverageTest.java
index 62158aa076..573a3785de 100644
--- a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/CodeCoverageTest.java
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/CodeCoverageTest.java
@@ -21,6 +21,7 @@
 
 import com.sonar.it.scanner.msbuild.utils.AnalysisContext;
 import com.sonar.it.scanner.msbuild.utils.AzureDevOps;
+import com.sonar.it.scanner.msbuild.utils.ContextExtension;
 import com.sonar.it.scanner.msbuild.utils.TestUtils;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -40,7 +41,7 @@
 import static org.assertj.core.api.Assertions.tuple;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-@ExtendWith(ServerTests.class)
+@ExtendWith({ServerTests.class, ContextExtension.class})
 class CodeCoverageTest {
   @TempDir
   public Path basePath;
@@ -71,8 +72,8 @@ void whenRunningOnAzureDevops_coverageIsImported() throws IOException {
     var context = createContextWithCoverage("whenRunningOnAzureDevops_coverageIsImported", buildDirectory);
     // Simulate Azure Devops: SonarQube.Integration.ImportBefore.targets determines paths based on these environment variables.
     var endLogs = context
-      .setEnvironmentVariable(AzureDevOps.TF_BUILD, "true")
-      .setEnvironmentVariable(AzureDevOps.BUILD_BUILDURI, "fake-uri")  //Must have value (can be anything)
+      .setEnvironmentVariable(AzureDevOps.TF_BUILD, "true")             // Simulate Azure Devops CI environment
+      .setEnvironmentVariable(AzureDevOps.BUILD_BUILDURI, "fake-uri")   //Must have value (can be anything)
       .setEnvironmentVariable(AzureDevOps.AGENT_BUILDDIRECTORY, buildDirectory.toString())   // The tests results should be present in a child "TestResults" folder.
       .setEnvironmentVariable(AzureDevOps.BUILD_SOURCESDIRECTORY, context.projectDir.toString())
       .runAnalysis()
@@ -179,7 +180,7 @@ void whenAddingCoverage_ExclusionsAreRespected(
   }
 
   private AnalysisContext createContextWithCoverage(String projectKey, Path buildDirectory) {
-    var context = AnalysisContext.forServer(projectKey, basePath, "CodeCoverage");
+    var context = AnalysisContext.forServer(basePath, "CodeCoverage");
     context.begin.setDebugLogs(); // For assertions
     // --collect "Code Coverage" parameter produces a binary coverage file ".coverage" that needs to be converted to an XML ".coveragexml" file by the end step
     context.build.useDotNet("test").addArgument("--collect", "Code Coverage", "--logger", "trx", "--results-directory", buildDirectory + "\\TestResults");
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/CppTest.java b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/CppTest.java
index 16f7b3af61..023c4e9a34 100644
--- a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/CppTest.java
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/CppTest.java
@@ -19,6 +19,7 @@
  */
 package com.sonar.it.scanner.msbuild.sonarqube;
 
+import com.sonar.it.scanner.msbuild.utils.ContextExtension;
 import com.sonar.it.scanner.msbuild.utils.TestUtils;
 import com.sonar.orchestrator.build.BuildResult;
 import com.sonar.orchestrator.util.ZipUtils;
@@ -41,7 +42,7 @@
  * Only cpp, without C# plugin
  */
 // See task https://github.com/SonarSource/sonar-scanner-msbuild/issues/789
-@ExtendWith(ServerTests.class)
+@ExtendWith({ServerTests.class, ContextExtension.class})
 class CppTest {
 
   @TempDir
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ExternalIssuesTest.java b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ExternalIssuesTest.java
index e361d49aa2..15ec16f400 100644
--- a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ExternalIssuesTest.java
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ExternalIssuesTest.java
@@ -19,6 +19,7 @@
  */
 package com.sonar.it.scanner.msbuild.sonarqube;
 
+import com.sonar.it.scanner.msbuild.utils.ContextExtension;
 import com.sonar.it.scanner.msbuild.utils.QualityProfiles;
 import com.sonar.it.scanner.msbuild.utils.ScannerClassifier;
 import com.sonar.it.scanner.msbuild.utils.TestUtils;
@@ -40,7 +41,7 @@
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assumptions.assumeFalse;
 
-@ExtendWith(ServerTests.class)
+@ExtendWith({ServerTests.class, ContextExtension.class})
 class ExternalIssuesTest {
   @TempDir
   public Path basePath;
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/IncrementalPRAnalysisTest.java b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/IncrementalPRAnalysisTest.java
index 30dde7f161..95404003ea 100644
--- a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/IncrementalPRAnalysisTest.java
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/IncrementalPRAnalysisTest.java
@@ -19,6 +19,7 @@
  */
 package com.sonar.it.scanner.msbuild.sonarqube;
 
+import com.sonar.it.scanner.msbuild.utils.ContextExtension;
 import com.sonar.it.scanner.msbuild.utils.TestUtils;
 import com.sonar.orchestrator.build.BuildResult;
 import com.sonar.orchestrator.http.HttpException;
@@ -41,7 +42,7 @@
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assumptions.assumeTrue;
 
-@ExtendWith(ServerTests.class)
+@ExtendWith({ServerTests.class, ContextExtension.class})
 class IncrementalPRAnalysisTest {
   final static Logger LOG = LoggerFactory.getLogger(IncrementalPRAnalysisTest.class);
 
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/JreProvisioningTest.java b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/JreProvisioningTest.java
index 854f9d3f38..ec7181c304 100644
--- a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/JreProvisioningTest.java
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/JreProvisioningTest.java
@@ -19,6 +19,7 @@
  */
 package com.sonar.it.scanner.msbuild.sonarqube;
 
+import com.sonar.it.scanner.msbuild.utils.ContextExtension;
 import com.sonar.it.scanner.msbuild.utils.TestUtils;
 import com.sonar.orchestrator.build.BuildResult;
 import java.io.IOException;
@@ -32,7 +33,7 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assumptions.assumeTrue;
 
-@ExtendWith(ServerTests.class)
+@ExtendWith({ServerTests.class, ContextExtension.class})
 public class JreProvisioningTest {
   private static final String PROJECT_NAME = "JreProvisioning";
 
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/MultiLanguageTest.java b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/MultiLanguageTest.java
index f44ba29317..1dc5c8da30 100644
--- a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/MultiLanguageTest.java
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/MultiLanguageTest.java
@@ -19,6 +19,7 @@
  */
 package com.sonar.it.scanner.msbuild.sonarqube;
 
+import com.sonar.it.scanner.msbuild.utils.ContextExtension;
 import com.sonar.it.scanner.msbuild.utils.QualityProfiles;
 import com.sonar.it.scanner.msbuild.utils.ScannerClassifier;
 import com.sonar.it.scanner.msbuild.utils.TestUtils;
@@ -46,7 +47,7 @@
 import static org.junit.jupiter.api.Assumptions.assumeFalse;
 import static org.junit.jupiter.api.Assumptions.assumeTrue;
 
-@ExtendWith(ServerTests.class)
+@ExtendWith({ServerTests.class, ContextExtension.class})
 class MultiLanguageTest {
   private static final String SONAR_RULES_PREFIX = "csharpsquid:";
 
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ParameterTest.java b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ParameterTest.java
index d41c556fa7..8535a203a4 100644
--- a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ParameterTest.java
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ParameterTest.java
@@ -20,6 +20,7 @@
 package com.sonar.it.scanner.msbuild.sonarqube;
 
 import com.eclipsesource.json.Json;
+import com.sonar.it.scanner.msbuild.utils.ContextExtension;
 import com.sonar.it.scanner.msbuild.utils.EnvironmentVariable;
 import com.sonar.it.scanner.msbuild.utils.QualityProfiles;
 import com.sonar.it.scanner.msbuild.utils.ScannerClassifier;
@@ -42,7 +43,7 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-@ExtendWith(ServerTests.class)
+@ExtendWith({ServerTests.class, ContextExtension.class})
 class ParameterTest {
   final static Logger LOG = LoggerFactory.getLogger(ParameterTest.class);
 
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ProxyTest.java b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ProxyTest.java
index ed69b856aa..876eedbd5b 100644
--- a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ProxyTest.java
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ProxyTest.java
@@ -19,6 +19,7 @@
  */
 package com.sonar.it.scanner.msbuild.sonarqube;
 
+import com.sonar.it.scanner.msbuild.utils.ContextExtension;
 import com.sonar.it.scanner.msbuild.utils.ProxyAuthenticator;
 import com.sonar.it.scanner.msbuild.utils.QualityProfiles;
 import com.sonar.it.scanner.msbuild.utils.TestUtils;
@@ -61,7 +62,7 @@
 import static com.sonar.it.scanner.msbuild.sonarqube.ServerTests.ORCHESTRATOR;
 import static org.assertj.core.api.Assertions.assertThat;
 
-@ExtendWith(ServerTests.class)
+@ExtendWith({ServerTests.class, ContextExtension.class})
 class ProxyTest {
   private static final String PROXY_USER = "scott";
   private static final String PROXY_PASSWORD = "tiger";
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/SQLServerTest.java b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/SQLServerTest.java
index bc1827eaee..c5cebf26b3 100644
--- a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/SQLServerTest.java
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/SQLServerTest.java
@@ -19,6 +19,7 @@
  */
 package com.sonar.it.scanner.msbuild.sonarqube;
 
+import com.sonar.it.scanner.msbuild.utils.ContextExtension;
 import com.sonar.it.scanner.msbuild.utils.TestUtils;
 import java.nio.file.Path;
 import java.util.List;
@@ -30,7 +31,7 @@
 import static com.sonar.it.scanner.msbuild.sonarqube.ServerTests.ORCHESTRATOR;
 import static org.assertj.core.api.Assertions.assertThat;
 
-@ExtendWith(ServerTests.class)
+@ExtendWith({ServerTests.class, ContextExtension.class})
 class SQLServerTest {
 
   @TempDir
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ScannerTest.java b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ScannerTest.java
index 43e57fda04..39974efaac 100644
--- a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ScannerTest.java
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/ScannerTest.java
@@ -20,6 +20,7 @@
 package com.sonar.it.scanner.msbuild.sonarqube;
 
 import com.sonar.it.scanner.msbuild.utils.AnalysisContext;
+import com.sonar.it.scanner.msbuild.utils.ContextExtension;
 import com.sonar.it.scanner.msbuild.utils.QualityProfiles;
 import com.sonar.it.scanner.msbuild.utils.ScannerClassifier;
 import com.sonar.it.scanner.msbuild.utils.ScannerCommand;
@@ -45,7 +46,7 @@
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assumptions.assumeTrue;
 
-@ExtendWith(ServerTests.class)
+@ExtendWith({ServerTests.class, ContextExtension.class})
 class ScannerTest {
   private static final String SONAR_RULES_PREFIX = "csharpsquid:";
 
@@ -54,7 +55,7 @@ class ScannerTest {
 
   @Test
   void testSample() {
-    var context = AnalysisContext.forServer("testSample", basePath, "ProjectUnderTest");
+    var context = AnalysisContext.forServer(basePath, "ProjectUnderTest");
     ORCHESTRATOR.getServer().provisionProject(context.projectKey, context.projectKey);
     ORCHESTRATOR.getServer().associateProjectToQualityProfile(context.projectKey, "cs", QualityProfiles.CS_S1134);
     var result = context.runAnalysis();
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
index 6795314900..e41c32aba4 100644
--- 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
@@ -19,7 +19,7 @@
  */
 package com.sonar.it.scanner.msbuild.sonarqube;
 
-import com.sonar.it.scanner.msbuild.utils.AnalysisContext;
+import com.sonar.it.scanner.msbuild.utils.ContextExtension;
 import com.sonar.it.scanner.msbuild.utils.ScannerClassifier;
 import com.sonar.it.scanner.msbuild.utils.TestUtils;
 import com.sonar.orchestrator.build.BuildResult;
@@ -45,7 +45,7 @@
 import static org.junit.jupiter.api.Assumptions.assumeFalse;
 import static org.junit.jupiter.api.Assumptions.assumeTrue;
 
-@ExtendWith(ServerTests.class)
+@ExtendWith({ServerTests.class, ContextExtension.class})
 class SolutionKindTest {
   private static final String SONAR_RULES_PREFIX = "csharpsquid:";
 
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/SslTest.java b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/SslTest.java
index 482a567c60..bef7d5f428 100644
--- a/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/SslTest.java
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/sonarqube/SslTest.java
@@ -20,6 +20,7 @@
 
 package com.sonar.it.scanner.msbuild.sonarqube;
 
+import com.sonar.it.scanner.msbuild.utils.ContextExtension;
 import com.sonar.it.scanner.msbuild.utils.EnvironmentVariable;
 import com.sonar.it.scanner.msbuild.utils.HttpsReverseProxy;
 import com.sonar.it.scanner.msbuild.utils.SslUtils;
@@ -43,7 +44,7 @@
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-@ExtendWith(ServerTests.class)
+@ExtendWith({ServerTests.class, ContextExtension.class})
 class SslTest {
   private static final Logger LOG = LoggerFactory.getLogger(SslTest.class);
   private static final String SSL_KEYSTORE_PASSWORD_ENV = "SSL_KEYSTORE_PASSWORD";
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/utils/AnalysisContext.java b/its/src/test/java/com/sonar/it/scanner/msbuild/utils/AnalysisContext.java
index b48103125e..28eacd102b 100644
--- a/its/src/test/java/com/sonar/it/scanner/msbuild/utils/AnalysisContext.java
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/utils/AnalysisContext.java
@@ -39,9 +39,9 @@ public class AnalysisContext {
   public final BuildCommand build;
   public final ScannerCommand end;
 
-  public AnalysisContext(Orchestrator orchestrator, ScannerClassifier classifier, String projectKey, Path projectDir, String token) {
+  public AnalysisContext(Orchestrator orchestrator, ScannerClassifier classifier, Path projectDir, String token) {
     this.orchestrator = orchestrator;
-    this.projectKey = projectKey;
+    this.projectKey = ContextExtension.currentTestName();
     this.projectDir = projectDir;
     this.token = token;
     begin = ScannerCommand.createBeginStep(classifier, token, projectDir, projectKey);
@@ -49,16 +49,16 @@ public AnalysisContext(Orchestrator orchestrator, ScannerClassifier classifier,
     end = ScannerCommand.createEndStep(classifier, token, projectDir);
   }
 
-  public static AnalysisContext forServer(String projectKey, Path temp, String directoryName) {
-    return forServer(projectKey, temp, directoryName, ScannerClassifier.NET_FRAMEWORK);
+  public static AnalysisContext forServer(Path temp, String directoryName) {
+    return forServer(temp, directoryName, ScannerClassifier.NET_FRAMEWORK);
   }
 
-  public static AnalysisContext forServer(String projectKey, Path temp, String directoryName, ScannerClassifier classifier) {
-    return new AnalysisContext(ServerTests.ORCHESTRATOR, classifier, projectKey, TestUtils.projectDir(temp, directoryName), ServerTests.token());
+  public static AnalysisContext forServer(Path temp, String directoryName, ScannerClassifier classifier) {
+    return new AnalysisContext(ServerTests.ORCHESTRATOR, classifier, TestUtils.projectDir(temp, directoryName), ServerTests.token());
   }
 
-  public static AnalysisContext forCloud(String projectKey, Path temp, String directoryName) {
-    var context = new AnalysisContext(null, ScannerClassifier.NET_FRAMEWORK, projectKey, TestUtils.projectDir(temp, directoryName), CloudConstants.SONARCLOUD_TOKEN);
+  public static AnalysisContext forCloud(Path temp, String directoryName) {
+    var context = new AnalysisContext(null, ScannerClassifier.NET_FRAMEWORK, TestUtils.projectDir(temp, directoryName), CloudConstants.SONARCLOUD_TOKEN);
     context.begin
       .setOrganization(CloudConstants.SONARCLOUD_ORGANIZATION)
       .setProperty("sonar.scanner.sonarcloudUrl", CloudConstants.SONARCLOUD_URL)
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/utils/BuildCommand.java b/its/src/test/java/com/sonar/it/scanner/msbuild/utils/BuildCommand.java
index 282c720b00..377ade66d1 100644
--- a/its/src/test/java/com/sonar/it/scanner/msbuild/utils/BuildCommand.java
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/utils/BuildCommand.java
@@ -61,7 +61,7 @@ public BuildResult execute(Orchestrator orchestrator) {
     // ToDo: SCAN4NET-312: Run restore & rebuild. Restore could be via NuGet (on request, or every time for simplicity?), MsBuild or dotnet
     var environmentVariables = environment.entrySet().stream().map(x -> new EnvironmentVariable(x.getKey(), x.getValue())).toList();
     var result = dotnetCommand == null
-      ? TestUtils.runMSBuild(orchestrator, projectDir, environmentVariables, timeout, prependArgument("/t:Restore,Rebuild"))
+      ? TestUtils.runMSBuild(orchestrator, projectDir, environmentVariables, timeout, appendArgument("/t:Restore,Rebuild"))
       : TestUtils.runDotnetCommand(projectDir, environmentVariables, dotnetCommand, dotnetArguments());
     assertTrue(result.isSuccess());
     return result;
@@ -69,14 +69,13 @@ public BuildResult execute(Orchestrator orchestrator) {
 
   private String[] dotnetArguments() {
     return dotnetCommand == "build"
-      ? prependArgument("--no-incremental")
+      ? appendArgument("--no-incremental")
       : arguments.toArray(new String[0]);
   }
 
-  private String[] prependArgument(String first) {
-    var all = new ArrayList<>();
-    all.add(first);
-    all.addAll(arguments);
+  private String[] appendArgument(String last) {
+    var all = new ArrayList<>(arguments);
+    all.add(last);
     return all.toArray(new String[0]);
   }
 
diff --git a/its/src/test/java/com/sonar/it/scanner/msbuild/utils/ContextExtension.java b/its/src/test/java/com/sonar/it/scanner/msbuild/utils/ContextExtension.java
new file mode 100644
index 0000000000..338478bfa9
--- /dev/null
+++ b/its/src/test/java/com/sonar/it/scanner/msbuild/utils/ContextExtension.java
@@ -0,0 +1,50 @@
+/*
+ * 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.utils;
+
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class ContextExtension implements BeforeEachCallback, AfterEachCallback {
+
+  // IT classes run in parallel, and this keeps track of the CURRENT value from the thread where the test started.
+  // Whenever a single test would use any form of multithreading, it can get a missing or wrong value here!
+  private static final ThreadLocal<String> currentTestName = new ThreadLocal<>();
+
+  @Override
+  public void beforeEach(ExtensionContext context) {
+    currentTestName.set(context.getRequiredTestMethod().getName());
+  }
+
+  @Override
+  public void afterEach(ExtensionContext context) {
+    currentTestName.remove();
+  }
+
+  public static String currentTestName() {
+    var name = currentTestName.get();
+    if (name == null) {
+      throw new RuntimeException("ContextExtension.currentTestName is not available. The test class is probably missing @ExtendWith({ContextExtension.class}).");
+    } else {
+      return name;
+    }
+  }
+}


Reviews:
user: login: martin-strecker-sonarsource
body: I think the Thread local approach is quite brittle. The `ParameterResolver` approach is technically the better approach.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T16:00:11Z
user: login: pavel-mikula-sonarsource
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T16:15:07Z
user: login: martin-strecker-sonarsource
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T16:38:32Z
user: login: pavel-mikula-sonarsource
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T16:49:27Z
user: login: pavel-mikula-sonarsource
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T16:49:43Z
user: login: pavel-mikula-sonarsource
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T16:50:12Z
user: login: martin-strecker-sonarsource
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T17:27:11Z
user: login: martin-strecker-sonarsource
body: Lets move forward with this, but please add a comment on the thread locals about the dangers associated with it.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T17:49:52Z
user: login: martin-strecker-sonarsource
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T09:58:52Z

Review comments:
diff_hunk: @@ -0,0 +1,48 @@
+/*
+ * 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.utils;
+
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class ContextExtension implements BeforeEachCallback, AfterEachCallback {
+
+  private static final ThreadLocal<String> currentTestName = new ThreadLocal<>();
path: its/src/test/java/com/sonar/it/scanner/msbuild/utils/ContextExtension.java
user: login: martin-strecker-sonarsource
body: That looks dangerous. If we would async/await inside the tests, the thread will change inside the test method and we will get wrong results.
One option would be using a [parameter](https://www.baeldung.com/junit-5-extensions#4-parameter-resolution) [resolver](https://www.baeldung.com/junit-5-parameters).
```java
public void TestMethod(AnalysisContextFactory factory)
{
    var analysisContext = factory.forServer(basePath, "ProjectUnderTest");
}

public class AnalysisContextFactory
{
    private final String projectName;
    public AnalysisContextFactory(String projectName) { .. }
    // All the factory methods go here
}

public class AnalysisContextFactoryParameterResolver: ParameterResolver
{
    @Override
    public boolean supportsParameter(ParameterContext parameterContext, 
      ExtensionContext extensionContext) throws ParameterResolutionException {
        return parameterContext.getParameter().getType()
          .equals(AnalysisContextFactory.class);
    }

    @Override
    public Object resolveParameter(ParameterContext parameterContext, 
      ExtensionContext extensionContext) throws ParameterResolutionException {
        return new AnalysisContextFactory(parameterContext.thisShouldAllowAccessToTheTestMethodNameSomeHow());
    }
}
```
created_at: 2025-03-26T15:55:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+/*
+ * 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.utils;
+
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class ContextExtension implements BeforeEachCallback, AfterEachCallback {
+
+  private static final ThreadLocal<String> currentTestName = new ThreadLocal<>();
path: its/src/test/java/com/sonar/it/scanner/msbuild/utils/ContextExtension.java
user: login: pavel-mikula-sonarsource
body: Yes, it's a bit brittle. But I don't think we will need async/await in S4NET ITs any time soon.

In the next PR, I'm using the same trick for managing the temp directory, which is easy to do that way. I don't see how to achieve the temp dir destruction with the parameter resolver approach.

In the sample, the test name could be read via `extensionContext.getRequiredTestMethod().getName()`
created_at: 2025-03-26T16:15:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+/*
+ * 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.utils;
+
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class ContextExtension implements BeforeEachCallback, AfterEachCallback {
+
+  private static final ThreadLocal<String> currentTestName = new ThreadLocal<>();
path: its/src/test/java/com/sonar/it/scanner/msbuild/utils/ContextExtension.java
user: login: martin-strecker-sonarsource
body: Good point regarding the clean-up. I see two options here:

 ### Use Auto-Close with the AnalysisContext

```java
try (var analysisContext = factory.forServer(basePath, "ProjectUnderTest")) {
}
```

### Use the `ExtensionContext.Store`


```java
public class AnalysisContextFactoryParameterResolver: ParameterResolver
{
    @Override
    public Object resolveParameter(ParameterContext parameterContext, 
      ExtensionContext extensionContext) throws ParameterResolutionException {
        var factory = new AnalysisContextFactory(extensionContext.getRequiredTestMethod().getName());
        // Store all factories in the global extension context
        extensionContext.getStore(Namespace.GLOBAL).get("factories").add(factory);
        return factory;
    }
}

public class CleanupExtension implements AfterAllCallback {
 @Override
  public void afterAll(ExtensionContext context) {
    extensionContext.getStore(Namespace.GLOBAL).get("factories").forEach(Directory.Delete);
  }
}
```

I don't know if its worth it, though.

Nevertheless, the `ExtensionContext.Store` seems also to be a good alternative to thread local storage.
created_at: 2025-03-26T16:38:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+/*
+ * 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.utils;
+
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class ContextExtension implements BeforeEachCallback, AfterEachCallback {
+
+  private static final ThreadLocal<String> currentTestName = new ThreadLocal<>();
path: its/src/test/java/com/sonar/it/scanner/msbuild/utils/ContextExtension.java
user: login: pavel-mikula-sonarsource
body: For the 1st one: I don't want the analysisContext to be disposable, because it should be the factory owning the resource. As the resource will then be also used directly by a few other methods that do not need the analysis context itself.

For the 2nd one: This would work. I just wanted to avoid the stores, as it adds some funky logic that is harder to follow. I don't fully understand how the GLOBAL think impacts the lifecycle, and what will happen in multithread.
created_at: 2025-03-26T16:49:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+/*
+ * 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.utils;
+
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class ContextExtension implements BeforeEachCallback, AfterEachCallback {
+
+  private static final ThreadLocal<String> currentTestName = new ThreadLocal<>();
path: its/src/test/java/com/sonar/it/scanner/msbuild/utils/ContextExtension.java
user: login: pavel-mikula-sonarsource
body: GH Copilot says that GLOBAL is shared between all tests and can lead to race conditions
created_at: 2025-03-26T16:49:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+/*
+ * 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.utils;
+
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class ContextExtension implements BeforeEachCallback, AfterEachCallback {
+
+  private static final ThreadLocal<String> currentTestName = new ThreadLocal<>();
path: its/src/test/java/com/sonar/it/scanner/msbuild/utils/ContextExtension.java
user: login: pavel-mikula-sonarsource
body: I'd keep it as is now
created_at: 2025-03-26T16:50:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+/*
+ * 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.utils;
+
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class ContextExtension implements BeforeEachCallback, AfterEachCallback {
+
+  private static final ThreadLocal<String> currentTestName = new ThreadLocal<>();
path: its/src/test/java/com/sonar/it/scanner/msbuild/utils/ContextExtension.java
user: login: martin-strecker-sonarsource
body: Fine with me. But you should leave a comment about threading issues that may lead to flakiness. It may help our future self's.
created_at: 2025-03-26T17:27:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+/*
+ * 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.utils;
+
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class ContextExtension implements BeforeEachCallback, AfterEachCallback {
+
+  private static final ThreadLocal<String> currentTestName = new ThreadLocal<>();
path: its/src/test/java/com/sonar/it/scanner/msbuild/utils/ContextExtension.java
user: login: martin-strecker-sonarsource
body: I did some quick tests with the junit extensions and their lifecycle:
* You can combine ParameterResolver with BeforeAllCallback and all the other extension interfaces (see e.g. [here](https://dzone.com/articles/lifecycle-of-junit-5s-extension-model))
* An extension is created once per test class and can therefore hold state in fields for all tests in a test class (I don't get the point of the extension context state because you can just use fields)

I keep the message here for future reference in case we want to touch the tempfile/test method name handling in the future.
created_at: 2025-03-27T09:58:52Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-03-27T08:37:09Z
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=2416) **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=2416&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=2416&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=2416&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=2416&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=2416&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=sonarscanner-msbuild&pullRequest=2416)


author_association: NONE