Skip to content

Commit

Permalink
Incremental PR analysis - SonarCloud: Add ITs (#1473)
Browse files Browse the repository at this point in the history
  • Loading branch information
costin-zaharia-sonarsource committed Mar 15, 2023
1 parent 0ca7c56 commit be65de1
Show file tree
Hide file tree
Showing 5 changed files with 287 additions and 6 deletions.
28 changes: 25 additions & 3 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ stages:
strategy:
matrix:
vs2017_latest89:
PRODUCT: "SonarQube"
SQ_VERSION: "LATEST_RELEASE[8.9]"
SONAR_CFAMILYPLUGIN_VERSION: "6.20.5.49286" # LATEST_RELEASE of CFAMILY is not compatible with old SQ
SONAR_CSHARPPLUGIN_VERSION: "DEV"
Expand All @@ -426,7 +427,9 @@ stages:
PLATFORMTOOLSET: "v140"
WINDOWSSDKTARGET: "10.0.17763.0"
JDKVERSION: "1.11"
TEST_SUITE: "**/SonarQubeTestSuite.java"
vs2019_latest79:
PRODUCT: "SonarQube"
SQ_VERSION: "LATEST_RELEASE[7.9]"
SONAR_CFAMILYPLUGIN_VERSION: "6.3.0.11371" # LATEST_RELEASE of CFAMILY is not compatible with old SQ
SONAR_CSHARPPLUGIN_VERSION: "DEV"
Expand All @@ -435,7 +438,9 @@ stages:
PLATFORMTOOLSET: "v140"
WINDOWSSDKTARGET: "10.0.17763.0"
JDKVERSION: "1.11"
TEST_SUITE: "**/SonarQubeTestSuite.java"
vs2022_latest89:
PRODUCT: "SonarQube"
SQ_VERSION: "LATEST_RELEASE[8.9]"
SONAR_CFAMILYPLUGIN_VERSION: "6.20.5.49286" # LATEST_RELEASE of CFAMILY is not compatible with old SQ
SONAR_CSHARPPLUGIN_VERSION: "DEV"
Expand All @@ -444,7 +449,9 @@ stages:
PLATFORMTOOLSET: "v140"
WINDOWSSDKTARGET: "10.0.17763.0"
JDKVERSION: "1.11"
TEST_SUITE: "**/SonarQubeTestSuite.java"
vs2022_latest:
PRODUCT: "SonarQube"
SQ_VERSION: "LATEST_RELEASE"
SONAR_CFAMILYPLUGIN_VERSION: "LATEST_RELEASE"
SONAR_CSHARPPLUGIN_VERSION: "DEV"
Expand All @@ -453,7 +460,9 @@ stages:
PLATFORMTOOLSET: "v140"
WINDOWSSDKTARGET: "10.0.17763.0"
JDKVERSION: "1.17"
TEST_SUITE: "**/SonarQubeTestSuite.java"
vs2022_dev:
PRODUCT: "SonarQube"
SQ_VERSION: "DEV"
SONAR_CFAMILYPLUGIN_VERSION: "LATEST_RELEASE"
SONAR_CSHARPPLUGIN_VERSION: "DEV"
Expand All @@ -462,6 +471,15 @@ stages:
PLATFORMTOOLSET: "v140"
WINDOWSSDKTARGET: "10.0.17763.0"
JDKVERSION: "1.17"
TEST_SUITE: "**/SonarQubeTestSuite.java"
vs2022_sonar_cloud:
PRODUCT: "SonarCloud"
SQ_VERSION: ""
MSBUILD_PATH: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\BuildTools\\MSBuild\\Current\\Bin\\MSBuild.exe"
PLATFORMTOOLSET: "v140"
WINDOWSSDKTARGET: "10.0.17763.0"
JDKVERSION: "1.17"
TEST_SUITE: "**/SonarCloudTestSuite.java"
variables:
MAVEN_CACHE_FOLDER: $(Pipeline.Workspace)/.m2/repository
MAVEN_OPTS: '-Xmx3072m -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)'
Expand Down Expand Up @@ -491,14 +509,17 @@ stages:
inputs:
versionSpec: '5.8.0'
- task: PowerShell@2
displayName: "Get version from artifact file"
displayName: "Get version from artifact file and unpack scanner"
inputs:
targetType: 'inline'
script: |
$projectVersion = Get-Content "$(Pipeline.Workspace)\\scanner-packages\\version.txt"
Write-Host "##vso[task.setvariable variable=SONAR_PROJECT_VERSION]$projectVersion"
Set-Location "$(Pipeline.Workspace)/scanner-packages"
Get-ChildItem -Filter *net46.zip | Select-Object -First 1 | Expand-Archive -DestinationPath scanner -Force
- task: Maven@3
displayName: 'Run Maven ITs for SQ $(SQ_VERSION)'
displayName: 'Run Maven ITs for $(PRODUCT) $(SQ_VERSION)'
env:
ARTIFACTORY_QA_READER_USERNAME: $(ARTIFACTORY_QA_READER_USERNAME)
ARTIFACTORY_QA_READER_PASSWORD: $(ARTIFACTORY_QA_READER_PASSWORD)
Expand All @@ -507,9 +528,10 @@ stages:
GITHUB_TOKEN: $(GITHUB_TOKEN)
MAVEN_LOCAL_REPOSITORY: $(MAVEN_CACHE_FOLDER)
NUGET_PATH: $(NUGETEXETOOLPATH)
SCANNER_PATH: "$(Pipeline.Workspace)/scanner-packages/scanner/SonarScanner.MSBuild.exe"
inputs:
goals: 'verify'
options: --settings $(mavenSettings.secureFilePath) -B -e -Denable-repo=qa -Dsonar.cfamilyplugin.version=$(SONAR_CFAMILYPLUGIN_VERSION) -Dsonar.csharpplugin.version=$(SONAR_CSHARPPLUGIN_VERSION) -Dsonar.vbnetplugin.version=$(SONAR_VBNETPLUGIN_VERSION) -Dsonar.runtimeVersion=$(SQ_VERSION) -DscannerForMSBuild.version=$(SONAR_PROJECT_VERSION).$(Build.BuildId) -Dmsbuild.path="$(MSBUILD_PATH)" -Dmsbuild.plateformtoolset=$(PLATFORMTOOLSET) -Dmsbuild.windowssdk=$(WINDOWSSDKTARGET)
options: --settings $(mavenSettings.secureFilePath) -B -e -Denable-repo=qa -DrunSuite=$(TEST_SUITE) -Dsonar.cfamilyplugin.version=$(SONAR_CFAMILYPLUGIN_VERSION) -Dsonar.csharpplugin.version=$(SONAR_CSHARPPLUGIN_VERSION) -Dsonar.vbnetplugin.version=$(SONAR_VBNETPLUGIN_VERSION) -Dsonar.runtimeVersion=$(SQ_VERSION) -DscannerForMSBuild.version=$(SONAR_PROJECT_VERSION).$(Build.BuildId) -Dmsbuild.path="$(MSBUILD_PATH)" -Dmsbuild.plateformtoolset=$(PLATFORMTOOLSET) -Dmsbuild.windowssdk=$(WINDOWSSDKTARGET)
publishJUnitResults: true
mavenPomFile: 'its/pom.xml'
testResultsFiles: '**/surefire-reports/TEST-*.xml'
Expand Down
3 changes: 2 additions & 1 deletion its/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<properties>
<jetty.version>11.0.12</jetty.version>
<jdk.min.version>11</jdk.min.version>
<runSuite>**/SonarQubeTestSuite.java,**/SonarCloudTestSuite.java,**/OthersTestSuite.java</runSuite>
</properties>

<dependencies>
Expand Down Expand Up @@ -93,7 +94,7 @@
<configuration>
<trimStackTrace>false</trimStackTrace>
<includes>
<include>**/SonarQubeTestSuite.java</include>
<include>${runSuite}</include>
</includes>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
/*
* SonarScanner for .NET
* Copyright (C) 2016-2023 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.sonarcloud;

import com.sonar.it.scanner.msbuild.utils.TestUtils;
import com.sonar.it.scanner.msbuild.utils.VstsUtils;
import com.sonar.orchestrator.http.HttpException;
import com.sonar.orchestrator.util.Command;
import com.sonar.orchestrator.util.CommandExecutor;
import com.sonar.orchestrator.util.StreamConsumer;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;

public class IncrementalPRAnalysisSonarCloudTest {
private final static Logger LOG = LoggerFactory.getLogger(IncrementalPRAnalysisSonarCloudTest.class);
private final static Integer COMMAND_TIMEOUT = 2 * 60 * 1000;
private final static String SCANNER_PATH = System.getenv("SCANNER_PATH") == null
? "../build/sonarscanner-msbuild-net46/SonarScanner.MSBuild.exe" // On the local machine, the scanner is prepared by ci-build.ps1 script.
: System.getenv("SCANNER_PATH");
private final static String[] prArguments = {
"/d:sonar.pullrequest.base=master",
"/d:sonar.pullrequest.branch=pull-request-branch",
"/d:sonar.pullrequest.key=pull-request-key"
};
private final static String SONARCLOUD_ORGANIZATION = System.getenv("SONARCLOUD_ORGANIZATION");
private final static String SONARCLOUD_PROJECT_KEY = System.getenv("SONARCLOUD_PROJECT_KEY");
private final static String SONARCLOUD_URL = System.getenv("SONARCLOUD_URL");
private final static String SONARCLOUD_PROJECT_TOKEN = System.getenv("SONARCLOUD_PROJECT_TOKEN");

@ClassRule
public static TemporaryFolder temp = TestUtils.createTempFolder();

@Test
public void master_emptyCache() throws IOException {
var projectDir = TestUtils.projectDir(temp, "IncrementalPRAnalysis");
var logWriter = new StringWriter();
StreamConsumer.Pipe logsConsumer = new StreamConsumer.Pipe(logWriter);

runBeginStep(projectDir, logsConsumer);

assertThat(logWriter.toString()).contains(
"Processing analysis cache",
"Incremental PR analysis: Base branch parameter was not provided.",
"Cache data is empty. A full analysis will be performed.");
}

@Test
public void prWithoutChanges_producesUnchangedFilesWithAllFiles() throws IOException {
var projectDir = TestUtils.projectDir(temp, "IncrementalPRAnalysis");

runAnalysis(projectDir); // Initial build - master.
var logs = runAnalysis(projectDir, prArguments); // PR analysis.

// Verify that the file hashes are considered and all of them will be skipped.
assertThat(logs).contains("Incremental PR analysis: 3 files out of 3 are unchanged.");
Path unchangedFilesPath = getUnchangedFilesPath(projectDir);
assertThat(Files.readString(unchangedFilesPath)).contains("Unchanged1.cs", "Unchanged2.cs", "WithChanges.cs");
}

@Test
public void prWithChanges_detectsUnchangedFile() throws IOException {
var projectDir = TestUtils.projectDir(temp, "IncrementalPRAnalysis");

runAnalysis(projectDir); // Initial build - master.
changeFile(projectDir, "IncrementalPRAnalysis\\WithChanges.cs"); // Change a file to force analysis.
runAnalysis(projectDir, prArguments); // PR analysis.

assertOnlyWithChangesFileIsConsideredChanged(projectDir);
}

@Test
public void prWithChanges_basedOnDifferentBranchThanMaster_detectsUnchangedFiles() throws IOException {
var projectDir = TestUtils.projectDir(temp, "IncrementalPRAnalysis");

runAnalysis(projectDir, "/d:sonar.branch.name=different-branch"); // Initial build - different branch.
changeFile(projectDir, "IncrementalPRAnalysis\\WithChanges.cs"); // Change a file to force analysis.
runAnalysis(projectDir, "/d:sonar.pullrequest.base=different-branch", "/d:sonar.pullrequest.branch=second-pull-request-branch", "/d:sonar.pullrequest.key=second-pull-request-key");

assertOnlyWithChangesFileIsConsideredChanged(projectDir);
}

private static void assertOnlyWithChangesFileIsConsideredChanged(Path projectDir) throws IOException {
Path unchangedFilesPath = getUnchangedFilesPath(projectDir);
LOG.info("UnchangedFiles: " + unchangedFilesPath.toAbsolutePath());
assertThat(unchangedFilesPath).exists();
assertThat(Files.readString(unchangedFilesPath))
.contains("Unchanged1.cs")
.contains("Unchanged2.cs")
.doesNotContain("WithChanges.cs");
}

private static void changeFile(Path projectDir, String filePath) throws IOException {
File fileToBeChanged = projectDir.resolve(filePath).toFile();
BufferedWriter writer = new BufferedWriter(new FileWriter(fileToBeChanged, true));
writer.append("\nclass Appended { /* FIXME: S1134 in third file that will have changes on PR */ }");
writer.close();
}

private static String runAnalysis(Path projectDir, String... arguments) {
var logWriter = new StringWriter();
StreamConsumer.Pipe logsConsumer = new StreamConsumer.Pipe(logWriter);

runBeginStep(projectDir, logsConsumer, arguments);
runBuild(projectDir, logsConsumer);
runEndStep(projectDir, logsConsumer);
var logs = logWriter.toString();
waitForTaskProcessing(logs);
return logs;
}

private static Path getUnchangedFilesPath(Path projectDir) {
Path buildDirectory = VstsUtils.isRunningUnderVsts() ? Path.of(VstsUtils.getEnvBuildDirectory()) : projectDir;
return buildDirectory.resolve(".sonarqube\\conf\\UnchangedFiles.txt").toAbsolutePath();
}

private static void runBeginStep(Path projectDir, StreamConsumer.Pipe logsConsumer, String... additionalArguments) {
var beginCommand = Command.create(new File(SCANNER_PATH).getAbsolutePath())
.setDirectory(projectDir.toFile())
.addArgument("begin")
.addArgument("/o:" + SONARCLOUD_ORGANIZATION)
.addArgument("/k:" + SONARCLOUD_PROJECT_KEY)
.addArgument("/d:sonar.host.url=" + SONARCLOUD_URL)
.addArgument("/d:sonar.login=" + SONARCLOUD_PROJECT_TOKEN)
.addArgument("/d:sonar.projectBaseDir=" + projectDir.toAbsolutePath())
.addArgument("/d:sonar.verbose=true");

for (var argument : additionalArguments)
{
beginCommand.addArgument(argument);
}

LOG.info("Scanner path: " + SCANNER_PATH);
LOG.info("Command line: " + beginCommand.toCommandLine());
var beginResult = CommandExecutor.create().execute(beginCommand, logsConsumer, COMMAND_TIMEOUT);
assertThat(beginResult).isZero();
}

private static void runEndStep(Path projectDir, StreamConsumer.Pipe logConsumer) {
var endCommand = Command.create(new File(SCANNER_PATH).getAbsolutePath())
.setDirectory(projectDir.toFile())
.addArgument("end")
.addArgument("/d:sonar.login=" + System.getenv("SONARCLOUD_PROJECT_TOKEN"));

var endResult = CommandExecutor.create().execute(endCommand, logConsumer, COMMAND_TIMEOUT);
assertThat(endResult).isZero();
}

private static void runBuild(Path projectDir, StreamConsumer.Pipe logConsumer) {
var buildCommand = Command.create(getMSBuildPath()).addArgument("/t:restore,build").setDirectory(projectDir.toFile());
int buildResult = CommandExecutor.create().execute(buildCommand, logConsumer, COMMAND_TIMEOUT);
assertThat(buildResult).isZero();
}

private static void waitForTaskProcessing(String logs) {
Pattern pattern = Pattern.compile("INFO: More about the report processing at (.*)");
Matcher matcher = pattern.matcher(logs);
if (matcher.find())
{
var uri = URI.create(matcher.group(1));
var client = HttpClient.newHttpClient();

await()
.pollInterval(Duration.ofSeconds(5))
.atMost(Duration.ofSeconds(120))
.until(() -> {
try
{
LOG.info("Pooling for task status using " + uri);
var request = HttpRequest.newBuilder(uri).header("Authorization", "Bearer " + SONARCLOUD_PROJECT_TOKEN).build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
return response.statusCode() == 200 && response.body().contains("\"status\":\"SUCCESS\"");
}
catch (HttpException ex) {
return false;
}
});
}
}

private static String getMSBuildPath() {
var msBuildPath = System.getenv("MSBUILD_PATH");
return msBuildPath == null
? TestUtils.MSBUILD_DEFAULT_PATH
: msBuildPath;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* SonarScanner for .NET
* Copyright (C) 2016-2023 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.sonarcloud;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(Suite.class)
@SuiteClasses({
IncrementalPRAnalysisSonarCloudTest.class
})
public class SonarCloudTestSuite {
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ public class TestUtils {
private static final String NUGET_PATH = "NUGET_PATH";
private static String token = null;

public static final String MSBUILD_DEFAULT_PATH = "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\MSBuild\\15.0\\Bin\\MSBuild.exe";

@CheckForNull
public static String getScannerVersion(Orchestrator orchestrator) {
return orchestrator.getConfiguration().getString("scannerForMSBuild.version");
Expand Down Expand Up @@ -316,8 +318,7 @@ public static BuildResult runMSBuildQuietly(Orchestrator orch, Path projectDir,

public static Path getMsBuildPath(Orchestrator orch) {
String msBuildPathStr = orch.getConfiguration().getString("msbuild.path",
orch.getConfiguration().getString("MSBUILD_PATH", "C:\\Program Files (x86)\\Microsoft Visual "
+ "Studio\\2017\\Enterprise\\MSBuild\\15.0\\Bin\\MSBuild.exe"));
orch.getConfiguration().getString("MSBUILD_PATH", MSBUILD_DEFAULT_PATH));
Path msBuildPath = Paths.get(msBuildPathStr).toAbsolutePath();
if (!Files.exists(msBuildPath)) {
throw new IllegalStateException("Unable to find MSBuild at " + msBuildPath
Expand Down

0 comments on commit be65de1

Please sign in to comment.