diff options
33 files changed, 417 insertions, 165 deletions
diff --git a/build.gradle b/build.gradle index 4a15ad007df..99cadc2ec9c 100644 --- a/build.gradle +++ b/build.gradle @@ -284,42 +284,44 @@ subprojects { dependencies { // bundled plugin list -- keep it alphabetically ordered dependency 'com.sonarsource.abap:sonar-abap-plugin:3.15.1.6010' + dependency 'com.sonarsource.armor:sonar-jasmin-plugin:1.3.0.6541' dependency 'com.sonarsource.cobol:sonar-cobol-plugin:5.8.1.8428' - dependency 'com.sonarsource.cpp:sonar-cfamily-dependencies-plugin:6.69.0.86956' - dependency 'com.sonarsource.cpp:sonar-cfamily-plugin:6.69.0.86956' + dependency 'com.sonarsource.cpp:sonar-cfamily-dependencies-plugin:6.70.0.87073' + dependency 'com.sonarsource.cpp:sonar-cfamily-plugin:6.70.0.87073' dependency 'com.sonarsource.dart:sonar-dart-plugin:1.3.0.2614' dependency 'com.sonarsource.dbd:sonar-dbd-plugin:2.2.0.16530' dependency 'com.sonarsource.dbd:sonar-dbd-java-frontend-plugin:2.2.0.16530' dependency 'com.sonarsource.dbd:sonar-dbd-python-frontend-plugin:2.2.0.16530' dependency 'com.sonarsource.dotnet:sonar-csharp-enterprise-plugin:10.15.0.120848' dependency 'com.sonarsource.dotnet:sonar-vbnet-enterprise-plugin:10.15.0.120848' - dependency 'com.sonarsource.go:sonar-go-enterprise-plugin:1.25.0.3305' + dependency 'com.sonarsource.go:sonar-go-enterprise-plugin:1.26.0.3421' dependency 'com.sonarsource.pli:sonar-pli-plugin:1.16.0.5325' dependency 'com.sonarsource.plsql:sonar-plsql-plugin:3.17.0.7448' dependency 'com.sonarsource.plugins.vb:sonar-vb-plugin:2.14.1.5552' dependency 'com.sonarsource.rpg:sonar-rpg-plugin:3.10.0.5337' - dependency 'com.sonarsource.security:sonar-security-csharp-frontend-plugin:11.5.0.38524' - dependency 'com.sonarsource.security:sonar-security-go-frontend-plugin:11.5.0.38524' - dependency 'com.sonarsource.security:sonar-security-java-frontend-plugin:11.5.0.38524' - dependency 'com.sonarsource.security:sonar-security-js-frontend-plugin:11.5.0.38524' - dependency 'com.sonarsource.security:sonar-security-kotlin-frontend-plugin:11.5.0.38524' - dependency 'com.sonarsource.security:sonar-security-php-frontend-plugin:11.5.0.38524' - dependency 'com.sonarsource.security:sonar-security-plugin:11.5.0.38524' - dependency 'com.sonarsource.security:sonar-security-python-frontend-plugin:11.5.0.38524' + dependency 'com.sonarsource.security:sonar-security-csharp-frontend-plugin:11.6.0.39346' + dependency 'com.sonarsource.security:sonar-security-go-frontend-plugin:11.6.0.39346' + dependency 'com.sonarsource.security:sonar-security-java-frontend-plugin:11.6.0.39346' + dependency 'com.sonarsource.security:sonar-security-js-frontend-plugin:11.6.0.39346' + dependency 'com.sonarsource.security:sonar-security-kotlin-frontend-plugin:11.6.0.39346' + dependency 'com.sonarsource.security:sonar-security-php-frontend-plugin:11.6.0.39346' + dependency 'com.sonarsource.security:sonar-security-plugin:11.6.0.39346' + dependency 'com.sonarsource.security:sonar-security-python-frontend-plugin:11.6.0.39346' + dependency 'com.sonarsource.security:sonar-security-vbnet-frontend-plugin:11.6.0.39346' dependency 'com.sonarsource.slang:sonar-apex-plugin:1.20.0.552' dependency 'org.sonarsource.slang:sonar-ruby-plugin:1.19.0.471' dependency 'org.sonarsource.slang:sonar-scala-plugin:1.19.0.484' dependency 'com.sonarsource.swift:sonar-swift-plugin:4.14.0.8764' - dependency 'com.sonarsource.tsql:sonar-tsql-plugin:1.14.1.7703' + dependency 'com.sonarsource.tsql:sonar-tsql-plugin:1.15.0.7898' dependency 'org.sonarsource.dotnet:sonar-csharp-plugin:10.15.0.120848' dependency 'org.sonarsource.dotnet:sonar-vbnet-plugin:10.15.0.120848' dependency 'org.sonarsource.flex:sonar-flex-plugin:2.14.0.5032' - dependency 'org.sonarsource.go:sonar-go-plugin:1.25.0.3305' + dependency 'org.sonarsource.go:sonar-go-plugin:1.26.0.3421' dependency 'org.sonarsource.html:sonar-html-plugin:3.19.0.5695' dependency 'org.sonarsource.jacoco:sonar-jacoco-plugin:1.3.0.1538' dependency 'org.sonarsource.java:sonar-java-plugin:8.18.0.40025' dependency 'org.sonarsource.java:sonar-java-symbolic-execution-plugin:8.16.0.131' - dependency 'org.sonarsource.javascript:sonar-javascript-plugin:11.1.0.33853' + dependency 'org.sonarsource.javascript:sonar-javascript-plugin:10.25.0.33900' dependency 'org.sonarsource.php:sonar-php-plugin:3.46.0.13151' dependency 'org.sonarsource.plugins.cayc:sonar-cayc-plugin:2.4.0.2018' dependency 'org.sonarsource.python:sonar-python-plugin:5.7.0.24163' @@ -329,15 +331,15 @@ subprojects { dependency "org.sonarsource.api.plugin:sonar-plugin-api:$pluginApiVersion" dependency "org.sonarsource.api.plugin:sonar-plugin-api-test-fixtures:$pluginApiVersion" dependency 'org.sonarsource.xml:sonar-xml-plugin:2.13.0.5938' - dependency 'org.sonarsource.iac:sonar-iac-plugin:1.47.0.15287' - dependency 'com.sonarsource.iac:sonar-iac-enterprise-plugin:1.47.0.15287' - dependency 'org.sonarsource.text:sonar-text-plugin:2.25.0.7448' - dependency 'com.sonarsource.text:sonar-text-developer-plugin:2.25.0.7448' - dependency 'com.sonarsource.text:sonar-text-enterprise-plugin:2.25.0.7448' + dependency 'org.sonarsource.iac:sonar-iac-plugin:1.48.0.15768' + dependency 'com.sonarsource.iac:sonar-iac-enterprise-plugin:1.48.0.15768' + dependency 'org.sonarsource.text:sonar-text-plugin:2.26.0.7517' + dependency 'com.sonarsource.text:sonar-text-developer-plugin:2.26.0.7517' + dependency 'com.sonarsource.text:sonar-text-enterprise-plugin:2.26.0.7517' dependency 'com.sonarsource.jcl:sonar-jcl-plugin:1.4.1.1493' - dependency 'com.sonarsource.architecture:sonar-architecture-plugin:1.11.0.5805' - dependency 'com.sonarsource.architecture:sonar-architecture-java-frontend-plugin:1.11.0.5805' - dependency 'com.sonarsource.architecture:sonar-architecture-javascript-frontend-plugin:1.11.0.5805' + dependency 'com.sonarsource.architecture:sonar-architecture-plugin:2.0.0.6303' + dependency 'com.sonarsource.architecture:sonar-architecture-java-frontend-plugin:2.0.0.6303' + dependency 'com.sonarsource.architecture:sonar-architecture-javascript-frontend-plugin:2.0.0.6303' // Webapp dependency "org.sonarsource.sonarqube:webapp-assets:$webappVersion" diff --git a/gradle.properties b/gradle.properties index ff6bb87972a..5bfe64dc9ec 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ version=25.8 # 6 months from the release date for non LTA versions # 18 months from the release date for LTA versions # No change required for patch versions -versionEOL=2027-02-01 +versionEOL=2026-04-01 pdfreportVersion=2.0.0.263 pluginApiVersion=13.0.0.3026 description=Open source platform for continuous inspection of code quality @@ -16,4 +16,4 @@ elasticSearchServerVersion=8.16.3 projectType=application artifactoryUrl=https://repox.jfrog.io/repox jre_release_name=jdk-17.0.13+11 -webappVersion=2025.4.0.22789 +webappVersion=2025.4.0.23179 diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/TaintChecker.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/TaintChecker.java index f40ddf46ccd..0ffc6baebed 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/TaintChecker.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/TaintChecker.java @@ -78,7 +78,7 @@ public class TaintChecker { private List<String> initializeRepositories() { List<String> repositories = new ArrayList<>(List.of("gosecurity", "javasecurity", "jssecurity", "kotlinsecurity", "phpsecurity", "pythonsecurity", - "roslyn.sonaranalyzer.security.cs", "tssecurity")); + "roslyn.sonaranalyzer.security.cs", "tssecurity", "vbnetsecurity")); if (!config.hasKey(EXTRA_TAINT_REPOSITORIES)) { return repositories; diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/TaintCheckerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/TaintCheckerTest.java index 4cbf7aeeb41..3a2471084ec 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/issue/TaintCheckerTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/TaintCheckerTest.java @@ -44,7 +44,7 @@ public class TaintCheckerTest { public void test_getTaintIssuesOnly() { List<IssueDto> taintIssues = underTest.getTaintIssuesOnly(getIssues()); - assertThat(taintIssues).hasSize(8); + assertThat(taintIssues).hasSize(9); assertThat(taintIssues.get(0).getKey()).isEqualTo("taintIssue1"); assertThat(taintIssues.get(1).getKey()).isEqualTo("taintIssue2"); assertThat(taintIssues.get(2).getKey()).isEqualTo("taintIssue3"); @@ -53,6 +53,7 @@ public class TaintCheckerTest { assertThat(taintIssues.get(5).getKey()).isEqualTo("taintIssue6"); assertThat(taintIssues.get(6).getKey()).isEqualTo("taintIssue7"); assertThat(taintIssues.get(7).getKey()).isEqualTo("taintIssue8"); + assertThat(taintIssues.get(8).getKey()).isEqualTo("taintIssue9"); } @Test @@ -71,7 +72,7 @@ public class TaintCheckerTest { Map<Boolean, List<IssueDto>> issuesByTaintStatus = underTest.mapIssuesByTaintStatus(getIssues()); assertThat(issuesByTaintStatus.keySet()).hasSize(2); - assertThat(issuesByTaintStatus.get(true)).hasSize(8); + assertThat(issuesByTaintStatus.get(true)).hasSize(9); assertThat(issuesByTaintStatus.get(false)).hasSize(3); assertThat(issuesByTaintStatus.get(true).get(0).getKey()).isEqualTo("taintIssue1"); @@ -91,9 +92,9 @@ public class TaintCheckerTest { @Test public void test_getTaintRepositories() { assertThat(underTest.getTaintRepositories()) - .hasSize(8) + .hasSize(9) .containsExactlyInAnyOrder("gosecurity", "javasecurity", "jssecurity", "kotlinsecurity", "phpsecurity", "pythonsecurity", - "roslyn.sonaranalyzer.security.cs", "tssecurity"); + "roslyn.sonaranalyzer.security.cs", "tssecurity", "vbnetsecurity"); } @Test @@ -102,9 +103,9 @@ public class TaintCheckerTest { when(configuration.getStringArray(EXTRA_TAINT_REPOSITORIES)).thenReturn(new String[]{"extra-1", "extra-2"}); TaintChecker underTest = new TaintChecker(configuration); assertThat(underTest.getTaintRepositories()) - .hasSize(10) + .hasSize(11) .containsExactlyInAnyOrder("gosecurity", "javasecurity", "jssecurity", "kotlinsecurity", "phpsecurity", "pythonsecurity", - "roslyn.sonaranalyzer.security.cs", "tssecurity", "extra-1", "extra-2"); + "roslyn.sonaranalyzer.security.cs", "tssecurity", "vbnetsecurity", "extra-1", "extra-2"); } @Test @@ -141,6 +142,7 @@ public class TaintCheckerTest { issues.add(createIssueWithRepository("taintIssue6", "pythonsecurity")); issues.add(createIssueWithRepository("taintIssue7", "kotlinsecurity")); issues.add(createIssueWithRepository("taintIssue8", "gosecurity")); + issues.add(createIssueWithRepository("taintIssue9", "vbnetsecurity")); issues.add(createIssueWithRepository("standardIssue1", "java")); issues.add(createIssueWithRepository("standardIssue2", "python")); diff --git a/server/sonar-webserver-common/src/it/java/org/sonar/server/common/component/ComponentUpdaterIT.java b/server/sonar-webserver-common/src/it/java/org/sonar/server/common/component/ComponentUpdaterIT.java index 809c680b6e2..82375857e19 100644 --- a/server/sonar-webserver-common/src/it/java/org/sonar/server/common/component/ComponentUpdaterIT.java +++ b/server/sonar-webserver-common/src/it/java/org/sonar/server/common/component/ComponentUpdaterIT.java @@ -40,6 +40,7 @@ import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentQualifiers; import org.sonar.db.component.ComponentScopes; +import org.sonar.db.portfolio.PortfolioDto; import org.sonar.db.project.CreationMethod; import org.sonar.db.project.ProjectDto; import org.sonar.db.user.UserDto; @@ -569,4 +570,34 @@ public class ComponentUpdaterIT { ProjectDto projectDto = underTest.create(db.getSession(), creationParameters).projectDto(); assertThat(projectDto.getCreationMethod()).isEqualTo(CreationMethod.ALM_IMPORT_BROWSER); } + + @Test + public void fail_to_create_portfolio_using_duplicate_child_portfolio_key() { + var parentPortfolio = db.components().insertPublicPortfolioDto(); + var childPortfolio = new PortfolioDto() + .setKey("ExistingChild") + .setName("name_" + "ExistingChild") + .setSelectionMode(PortfolioDto.SelectionMode.NONE) + .setRootUuid(parentPortfolio.getRootUuid()) + .setParentUuid(parentPortfolio.getUuid()) + .setUuid("uuid_" + "ExistingChild"); + DbSession session = db.getSession(); + db.getDbClient().portfolioDao().insertWithAudit(session, childPortfolio); + session.commit(); + + var newPortfolio = NewComponent.newComponentBuilder() + .setKey(childPortfolio.getKey()) + .setName("New Portfolio") + .setQualifier(VIEW) + .build(); + var creationParameters = ComponentCreationParameters.builder() + .newComponent(newPortfolio) + .creationMethod(CreationMethod.LOCAL_API) + .mainBranchName("main") + .build(); + + assertThatThrownBy(() -> underTest.create(session, creationParameters)) + .isInstanceOf(BadRequestException.class) + .hasMessage("Could not create component with key: \"%s\". Key already in use.", childPortfolio.getKey()); + } } diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/ComponentUpdater.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/ComponentUpdater.java index 10c39b9a7a4..6e7a0c00fa1 100644 --- a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/ComponentUpdater.java +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/component/ComponentUpdater.java @@ -185,8 +185,11 @@ public class ComponentUpdater { } private void checkKeyAlreadyExists(DbSession dbSession, NewComponent newComponent) { + Optional<PortfolioDto> portfolios = dbClient.portfolioDao().selectByKey(dbSession, newComponent.key()); + if (portfolios.isPresent()) { + throwBadRequestException("Could not create component with key: \"%s\". Key already in use.", newComponent.key()); + } List<ComponentDto> componentDtos = dbClient.componentDao().selectByKeyCaseInsensitive(dbSession, newComponent.key()); - if (!componentDtos.isEmpty()) { String alreadyExistingKeys = componentDtos .stream() diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java index f1d99c660b3..39e1189e5a6 100644 --- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java +++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java @@ -31,7 +31,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; -import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -46,7 +45,6 @@ import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.db.component.ComponentQualifiers; import org.sonar.api.rule.RuleKey; import org.sonar.core.rule.RuleType; @@ -71,8 +69,6 @@ import static java.lang.String.format; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static org.sonar.api.issue.Issue.STATUSES; -import static org.sonar.api.issue.Issue.STATUS_CONFIRMED; -import static org.sonar.api.issue.Issue.STATUS_OPEN; import static org.sonar.api.issue.Issue.STATUS_REVIEWED; import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW; import static org.sonar.api.measures.CoreMetrics.ANALYSIS_FROM_SONARQUBE_9_4_KEY; @@ -185,39 +181,6 @@ public class IssueQueryFactory { } } - public IssueQuery openIssueCountBySeverity( - String projectUuid, - String branchUuid, - String componentUuid, - boolean isMainBranch, - boolean newCode, - SoftwareQuality softwareQuality - ) { - var timeZone = clock.getZone(); - - var types = EnumSet.complementOf(EnumSet.of(RuleType.SECURITY_HOTSPOT)) - .stream() - .map(RuleType::name) - .toList(); - - var query = IssueQuery.builder() - .branchUuid(branchUuid) - .mainBranch(isMainBranch) - .issueStatuses(List.of(STATUS_OPEN, STATUS_CONFIRMED)) - .impactSoftwareQualities(List.of(softwareQuality.name())) - .projectUuids(List.of(projectUuid)) - .timeZone(timeZone) - .types(types); - - if (newCode) { - try (DbSession dbSession = dbClient.openSession(false)) { - setInNewCodePeriod(dbSession, query, componentUuid); - } - } - - return query.build(); - } - private Collection<String> collectIssueKeys(DbSession dbSession, SearchRequest request) { Collection<String> issueKeys = null; if (request.getFixedInPullRequest() != null) { diff --git a/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ServerMonitoringMetrics.java b/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ServerMonitoringMetrics.java index 9d6c9c9ebc5..39f91037d8c 100644 --- a/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ServerMonitoringMetrics.java +++ b/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ServerMonitoringMetrics.java @@ -37,6 +37,7 @@ public class ServerMonitoringMetrics { private final Gauge cePendingTasksTotal; private final Summary ceTasksRunningDuration; + private final Summary ceSystemTasksRunningDuration; private final Gauge elasticsearchDiskSpaceFreeBytesGauge; private final Gauge elasticSearchDiskSpaceTotalBytes; @@ -80,6 +81,12 @@ public class ServerMonitoringMetrics { .labelNames("task_type", "project_key") .register(); + ceSystemTasksRunningDuration = Summary.build() + .name("sonarqube_compute_engine_system_tasks_running_duration_seconds") + .help("Compute engine system task running time in seconds") + .labelNames("task_type") + .register(); + computeEngineGauge = Gauge.build() .name("sonarqube_health_compute_engine_status") .help("Tells whether Compute Engine is up (healthy, ready to take tasks) or down. 1 for up, 0 for down") @@ -165,8 +172,12 @@ public class ServerMonitoringMetrics { cePendingTasksTotal.set(numberOfPendingTasks); } - public void observeComputeEngineTaskDuration(long durationInSeconds, String taskType, String projectKey) { - ceTasksRunningDuration.labels(taskType, projectKey).observe(durationInSeconds); + public void observeComputeEngineTaskDuration(long durationInSeconds, String taskType, String label) { + ceTasksRunningDuration.labels(taskType, label).observe(durationInSeconds); + } + + public void observeComputeEngineSystemTaskDuration(long durationInSeconds, String taskType) { + ceSystemTasksRunningDuration.labels(taskType).observe(durationInSeconds); } public void setComputeEngineStatusToGreen() { diff --git a/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ce/RecentTasksDurationTask.java b/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ce/RecentTasksDurationTask.java index 74d3dd85387..5a4c22d15d8 100644 --- a/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ce/RecentTasksDurationTask.java +++ b/server/sonar-webserver-monitoring/src/main/java/org/sonar/server/monitoring/ce/RecentTasksDurationTask.java @@ -22,11 +22,12 @@ package org.sonar.server.monitoring.ce; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; -import org.sonar.api.config.Configuration; -import org.sonar.api.utils.System2; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.sonar.api.config.Configuration; +import org.sonar.api.utils.System2; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.ce.CeActivityDto; @@ -56,6 +57,7 @@ public class RecentTasksDurationTask extends ComputeEngineMetricsTask { Collection<String> entityUuids = recentSuccessfulTasks.stream() .map(CeActivityDto::getEntityUuid) + .filter(Objects::nonNull) .toList(); List<EntityDto> entities = dbClient.entityDao().selectByUuids(dbSession, entityUuids); Map<String, String> entityUuidAndKeys = entities.stream() @@ -75,20 +77,22 @@ public class RecentTasksDurationTask extends ComputeEngineMetricsTask { private void reportObservedDurationForTasks(List<CeActivityDto> tasks, Map<String, String> entityUuidAndKeys) { for (CeActivityDto task : tasks) { - String mainComponentUuid = task.getEntityUuid(); + String entityUuid = task.getEntityUuid(); Long executionTimeMs = task.getExecutionTimeMs(); try { - requireNonNull(mainComponentUuid); requireNonNull(executionTimeMs); - String mainComponentKey = entityUuidAndKeys.get(mainComponentUuid); - requireNonNull(mainComponentKey); - - metrics.observeComputeEngineTaskDuration(executionTimeMs, task.getTaskType(), mainComponentKey); + if (entityUuid != null) { + String label = entityUuidAndKeys.get(entityUuid); + requireNonNull(label); + metrics.observeComputeEngineTaskDuration(executionTimeMs, task.getTaskType(), label); + } else { + metrics.observeComputeEngineSystemTaskDuration(executionTimeMs, task.getTaskType()); + } } catch (RuntimeException e) { - LOGGER.warn("Can't report metric data for a CE task with component uuid " + mainComponentUuid, e); + LOGGER.warn("Can't report metric data for a CE task with entity uuid " + entityUuid, e); } } - } + } diff --git a/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ServerMonitoringMetricsTest.java b/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ServerMonitoringMetricsTest.java index 8b0cbd624f0..ecacceefccb 100644 --- a/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ServerMonitoringMetricsTest.java +++ b/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ServerMonitoringMetricsTest.java @@ -158,6 +158,18 @@ public class ServerMonitoringMetricsTest { labelNames, labelValues)).isEqualTo(10); } + @Test + public void observeComputeEngineSystemTaskDurationTest() { + ServerMonitoringMetrics metrics = new ServerMonitoringMetrics(); + String[] labelNames = {"task_type"}; + String[] labelValues = {"AUDIT_PURGE"}; + + metrics.observeComputeEngineSystemTaskDuration(10, labelValues[0]); + + assertThat(CollectorRegistry.defaultRegistry.getSampleValue("sonarqube_compute_engine_system_tasks_running_duration_seconds_sum", + labelNames, labelValues)).isEqualTo(10); + } + private int sizeOfDefaultRegistry() { Enumeration<Collector.MetricFamilySamples> metrics = CollectorRegistry.defaultRegistry.metricFamilySamples(); return Collections.list(metrics).size(); diff --git a/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ce/RecentTasksDurationTaskTest.java b/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ce/RecentTasksDurationTaskTest.java index 5b5fc0a3570..7fdbfaa9dee 100644 --- a/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ce/RecentTasksDurationTaskTest.java +++ b/server/sonar-webserver-monitoring/src/test/java/org/sonar/server/monitoring/ce/RecentTasksDurationTaskTest.java @@ -86,6 +86,22 @@ public class RecentTasksDurationTaskTest { } @Test + public void run_given1TaskWithEntityUuidAnd1Without_observeDurationFor2Tasks() { + RecentTasksDurationTask task = new RecentTasksDurationTask(dbClient, metrics, config, system); + List<CeActivityDto> recentTasks = createTasks(2, 0); + + recentTasks.get(0).setEntityUuid(null); + + when(entityDao.selectByUuids(any(), any())).thenReturn(createEntityDtos(1)); + when(ceActivityDao.selectNewerThan(any(), anyLong())).thenReturn(recentTasks); + + task.run(); + + verify(metrics, times(1)).observeComputeEngineTaskDuration(anyLong(), any(), any()); + verify(metrics, times(1)).observeComputeEngineSystemTaskDuration(anyLong(), any()); + } + + @Test public void run_givenNullExecutionTime_dontReportMetricData() { RecentTasksDurationTask task = new RecentTasksDurationTask(dbClient, metrics, config, system); List<CeActivityDto> recentTasks = createTasks(1, 0); diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/email/config/controller/EmailConfigurationController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/email/config/controller/EmailConfigurationController.java index a62ec04c33a..8fb0f5e05a3 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/email/config/controller/EmailConfigurationController.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/email/config/controller/EmailConfigurationController.java @@ -30,7 +30,6 @@ import org.sonar.server.v2.api.email.config.request.EmailConfigurationUpdateRest import org.sonar.server.v2.api.email.config.resource.EmailConfigurationResource; import org.sonar.server.v2.api.email.config.response.EmailConfigurationSearchRestResponse; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -44,6 +43,7 @@ import org.springframework.web.bind.annotation.RestController; import static org.sonar.server.v2.WebApiEndpoints.EMAIL_CONFIGURATION_ENDPOINT; import static org.sonar.server.v2.WebApiEndpoints.INTERNAL; import static org.sonar.server.v2.WebApiEndpoints.JSON_MERGE_PATCH_CONTENT_TYPE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @RequestMapping(EMAIL_CONFIGURATION_ENDPOINT) @RestController @@ -58,7 +58,7 @@ public interface EmailConfigurationController { extensions = @Extension(properties = {@ExtensionProperty(name = INTERNAL, value = "true")})) EmailConfigurationResource createEmailConfiguration(@Valid @RequestBody EmailConfigurationCreateRestRequest createRequest); - @GetMapping(path = "/{id}") + @GetMapping(path = "/{id}", produces = APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) @Operation(summary = "Fetch an email configuration", description = """ Fetch a Email configuration. Requires 'Administer System' permission. @@ -76,7 +76,7 @@ public interface EmailConfigurationController { extensions = @Extension(properties = {@ExtensionProperty(name = INTERNAL, value = "true")})) EmailConfigurationSearchRestResponse searchEmailConfigurations(); - @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = MediaType.APPLICATION_JSON_VALUE) + @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) @Operation(summary = "Update an email configuration", description = """ Update an email configuration. Requires 'Administer System' permission. diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/GithubConfigurationController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/GithubConfigurationController.java index dda8fdbc4b7..d246cb4af26 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/GithubConfigurationController.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/github/config/controller/GithubConfigurationController.java @@ -30,7 +30,6 @@ import org.sonar.server.v2.api.github.config.request.GithubConfigurationUpdateRe import org.sonar.server.v2.api.github.config.resource.GithubConfigurationResource; import org.sonar.server.v2.api.github.config.response.GithubConfigurationSearchRestResponse; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -44,12 +43,13 @@ import org.springframework.web.bind.annotation.RestController; import static org.sonar.server.v2.WebApiEndpoints.GITHUB_CONFIGURATION_ENDPOINT; import static org.sonar.server.v2.WebApiEndpoints.INTERNAL; import static org.sonar.server.v2.WebApiEndpoints.JSON_MERGE_PATCH_CONTENT_TYPE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @RequestMapping(GITHUB_CONFIGURATION_ENDPOINT) @RestController public interface GithubConfigurationController { - @GetMapping(path = "/{id}") + @GetMapping(path = "/{id}", produces = APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) @Operation(summary = "Fetch a GitHub configuration", description = """ Fetch a GitHub configuration. Requires 'Administer System' permission. @@ -67,7 +67,7 @@ public interface GithubConfigurationController { extensions = @Extension(properties = {@ExtensionProperty(name = INTERNAL, value = "true")})) GithubConfigurationSearchRestResponse searchGithubConfiguration(); - @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = MediaType.APPLICATION_JSON_VALUE) + @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) @Operation(summary = "Update a GitHub configuration", description = """ Update a GitHub configuration. Requires 'Administer System' permission. diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/controller/GitlabConfigurationController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/controller/GitlabConfigurationController.java index a00f7cd0891..b3ebde2230f 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/controller/GitlabConfigurationController.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/gitlab/config/controller/GitlabConfigurationController.java @@ -30,7 +30,6 @@ import org.sonar.server.v2.api.gitlab.config.request.GitlabConfigurationUpdateRe import org.sonar.server.v2.api.gitlab.config.resource.GitlabConfigurationResource; import org.sonar.server.v2.api.gitlab.config.response.GitlabConfigurationSearchRestResponse; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -44,12 +43,13 @@ import org.springframework.web.bind.annotation.RestController; import static org.sonar.server.v2.WebApiEndpoints.GITLAB_CONFIGURATION_ENDPOINT; import static org.sonar.server.v2.WebApiEndpoints.INTERNAL; import static org.sonar.server.v2.WebApiEndpoints.JSON_MERGE_PATCH_CONTENT_TYPE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @RequestMapping(GITLAB_CONFIGURATION_ENDPOINT) @RestController public interface GitlabConfigurationController { - @GetMapping(path = "/{id}") + @GetMapping(path = "/{id}", produces = APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) @Operation(summary = "Fetch a GitLab configuration", description = """ Fetch a GitLab configuration. Requires 'Administer System' permission. @@ -67,7 +67,7 @@ public interface GitlabConfigurationController { extensions = @Extension(properties = {@ExtensionProperty(name = INTERNAL, value = "true")})) GitlabConfigurationSearchRestResponse searchGitlabConfiguration(); - @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = MediaType.APPLICATION_JSON_VALUE) + @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) @Operation(summary = "Update a Gitlab configuration", description = """ Update a Gitlab configuration. Requires 'Administer System' permission. diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/controller/GroupController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/controller/GroupController.java index f6f6a149679..66bda515c44 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/controller/GroupController.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/group/controller/GroupController.java @@ -35,7 +35,6 @@ import org.sonar.server.v2.api.group.response.GroupsSearchRestResponse; import org.sonar.server.v2.api.model.RestPage; import org.springdoc.core.annotations.ParameterObject; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -49,12 +48,13 @@ import org.springframework.web.bind.annotation.RestController; import static org.sonar.server.v2.WebApiEndpoints.GROUPS_ENDPOINT; import static org.sonar.server.v2.WebApiEndpoints.JSON_MERGE_PATCH_CONTENT_TYPE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @RequestMapping(GROUPS_ENDPOINT) @RestController public interface GroupController { - @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + @GetMapping(produces = APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) @Operation(summary = "Group search", description = """ Get the list of groups. @@ -66,12 +66,12 @@ public interface GroupController { extensions = @Extension(properties = {@ExtensionProperty(name = "internal", value = "true")}), hidden = true) String excludedUserId, @Valid @ParameterObject RestPage restPage); - @GetMapping(path = "/{id}") + @GetMapping(path = "/{id}", produces = APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) @Operation(summary = "Fetch a single group", description = "Fetch a single group.") GroupRestResponse fetchGroup(@PathVariable("id") @Parameter(description = "The id of the group to fetch.", required = true, in = ParameterIn.PATH) String id); - @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + @PostMapping(consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.CREATED) @Operation(summary = "Create a new group", description = "Create a new group.") GroupRestResponse create(@Valid @RequestBody GroupCreateRestRequest request); @@ -81,7 +81,7 @@ public interface GroupController { @Operation(summary = "Deletes a group", description = "Deletes a group.") void deleteGroup(@PathVariable("id") @Parameter(description = "The ID of the group to delete.", required = true, in = ParameterIn.PATH) String id); - @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = MediaType.APPLICATION_JSON_VALUE) + @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) @Operation(summary = "Update a group", description = """ Update a group name or description. diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/mode/controller/ModeController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/mode/controller/ModeController.java index 336584a778a..3dec8655db4 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/mode/controller/ModeController.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/mode/controller/ModeController.java @@ -34,12 +34,13 @@ import org.springframework.web.bind.annotation.RestController; import static org.sonar.server.v2.WebApiEndpoints.INTERNAL; import static org.sonar.server.v2.WebApiEndpoints.MODE_ENDPOINT; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @RequestMapping(MODE_ENDPOINT) @RestController public interface ModeController { - @GetMapping(path = "") + @GetMapping(path = "", produces = APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) @Operation(summary = "Retrieve current instance Mode", description = """ Fetch the current instance mode. Can be Multi-Quality Rules (MQR) Mode or Standard Experience. diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projectbindings/controller/ProjectBindingsController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projectbindings/controller/ProjectBindingsController.java index 64d3eda84b0..8a7ca41d895 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projectbindings/controller/ProjectBindingsController.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/projectbindings/controller/ProjectBindingsController.java @@ -37,12 +37,13 @@ import org.springframework.web.bind.annotation.RestController; import static org.sonar.server.v2.WebApiEndpoints.INTERNAL; import static org.sonar.server.v2.WebApiEndpoints.PROJECT_BINDINGS_ENDPOINT; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @RequestMapping(PROJECT_BINDINGS_ENDPOINT) @RestController public interface ProjectBindingsController { - @GetMapping(path = "/{id}") + @GetMapping(path = "/{id}", produces = APPLICATION_JSON_VALUE) @Operation( operationId = "getProjectBinding", summary = "Fetch a single Project Binding", diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/user/controller/UserController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/user/controller/UserController.java index 52563f6d997..1e478f0b602 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/user/controller/UserController.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/user/controller/UserController.java @@ -35,7 +35,6 @@ import org.sonar.server.v2.api.user.response.UserRestResponse; import org.sonar.server.v2.api.user.response.UsersSearchRestResponse; import org.springdoc.core.annotations.ParameterObject; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -49,12 +48,13 @@ import org.springframework.web.bind.annotation.RestController; import static org.sonar.server.v2.WebApiEndpoints.JSON_MERGE_PATCH_CONTENT_TYPE; import static org.sonar.server.v2.WebApiEndpoints.USER_ENDPOINT; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @RequestMapping(USER_ENDPOINT) @RestController public interface UserController { - @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + @GetMapping(produces = APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) @Operation(summary = "Users search", description = """ Get a list of users. By default, only active users are returned. @@ -82,7 +82,7 @@ public interface UserController { @PathVariable("id") @Parameter(description = "The ID of the user to delete.", required = true, in = ParameterIn.PATH) String id, @RequestParam(value = "anonymize", required = false, defaultValue = "false") @Parameter(description = "Anonymize user in addition to deactivating it.") Boolean anonymize); - @GetMapping(path = "/{id}") + @GetMapping(path = "/{id}", produces = APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) @Operation(summary = "Fetch a single user", description = """ Fetch a single user. @@ -98,14 +98,14 @@ public interface UserController { """) UserRestResponse fetchUser(@PathVariable("id") @Parameter(description = "The id of the user to fetch.", required = true, in = ParameterIn.PATH) String id); - @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = MediaType.APPLICATION_JSON_VALUE) + @PatchMapping(path = "/{id}", consumes = JSON_MERGE_PATCH_CONTENT_TYPE, produces = APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) @Operation(summary = "Update a user", description = """ Update users attributes. """) UserRestResponse updateUser(@PathVariable("id") String id, @Valid @RequestBody UserUpdateRestRequest updateRequest); - @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + @PostMapping(consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.OK) @Operation(summary = "User creation", description = """ Create a user. diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/CspFilter.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/CspFilter.java index 7015b1a0b46..195aa01c6bf 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/CspFilter.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/web/CspFilter.java @@ -35,8 +35,6 @@ import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletResponse; public class CspFilter implements Filter { - private static final String BEAMER_SRC = " https://*.getbeamer.com "; - private final List<String> cspHeaders = new ArrayList<>(); private String policies = null; @@ -48,13 +46,13 @@ public class CspFilter implements Filter { cspPolicies.add("default-src 'self'"); cspPolicies.add("base-uri 'none'"); cspPolicies.add("connect-src 'self' http: https:"); - cspPolicies.add("font-src 'self' data:" + BEAMER_SRC); - cspPolicies.add("frame-src" + BEAMER_SRC); + cspPolicies.add("font-src 'self' data:"); + cspPolicies.add("frame-src"); cspPolicies.add("img-src * data: blob:"); cspPolicies.add("object-src 'none'"); // the hash below corresponds to the window.__assetsPath script in index.html - cspPolicies.add("script-src 'self'" + BEAMER_SRC + getAssetsPathScriptCSPHash(filterConfig.getServletContext().getContextPath())); - cspPolicies.add("style-src 'self' 'unsafe-inline'" + BEAMER_SRC); + cspPolicies.add("script-src 'self' " + getAssetsPathScriptCSPHash(filterConfig.getServletContext().getContextPath())); + cspPolicies.add("style-src 'self' 'unsafe-inline'"); cspPolicies.add("worker-src 'self'"); this.policies = String.join("; ", cspPolicies).trim(); } diff --git a/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/CspFilterTest.java b/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/CspFilterTest.java index b2ef0146d4e..c4e0b3cfcd4 100644 --- a/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/CspFilterTest.java +++ b/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/CspFilterTest.java @@ -41,12 +41,12 @@ public class CspFilterTest { private static final String EXPECTED = "default-src 'self'; " + "base-uri 'none'; " + "connect-src 'self' http: https:; " + - "font-src 'self' data: https://*.getbeamer.com ; " + - "frame-src https://*.getbeamer.com ; " + + "font-src 'self' data:; " + + "frame-src; " + "img-src * data: blob:; " + "object-src 'none'; " + - "script-src 'self' https://*.getbeamer.com 'sha256-hK8SVWFNHY0UhP61DBzX/3fvT74EI8u6/jRQvUKeZoU='; " + - "style-src 'self' 'unsafe-inline' https://*.getbeamer.com ; " + + "script-src 'self' 'sha256-hK8SVWFNHY0UhP61DBzX/3fvT74EI8u6/jRQvUKeZoU='; " + + "style-src 'self' 'unsafe-inline'; " + "worker-src 'self'"; private final ServletContext servletContext = mock(ServletContext.class, RETURNS_MOCKS); private final HttpServletResponse response = mock(HttpServletResponse.class); @@ -75,7 +75,7 @@ public class CspFilterTest { doInit(); HttpServletRequest request = newRequest("/"); underTest.doFilter(request, response, chain); - verify(response).setHeader(eq("Content-Security-Policy"), contains("script-src 'self' https://*.getbeamer.com 'sha256-D1jaqcDDM2TM2STrzE42NNqyKR9PlptcHDe6tyaBcuM='; ")); + verify(response).setHeader(eq("Content-Security-Policy"), contains("script-src 'self' 'sha256-D1jaqcDDM2TM2STrzE42NNqyKR9PlptcHDe6tyaBcuM='; ")); verify(chain).doFilter(request, response); } diff --git a/sonar-core/src/main/java/org/sonar/core/util/ProcessWrapperFactory.java b/sonar-core/src/main/java/org/sonar/core/util/ProcessWrapperFactory.java index 12abd1396c0..d68ebbc01f1 100644 --- a/sonar-core/src/main/java/org/sonar/core/util/ProcessWrapperFactory.java +++ b/sonar-core/src/main/java/org/sonar/core/util/ProcessWrapperFactory.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import javax.annotation.Nullable; import org.apache.commons.exec.CommandLine; @@ -62,6 +63,7 @@ public class ProcessWrapperFactory { private final Consumer<String> stdErrLineConsumer; private final String[] command; private final Map<String, String> envVariables = new HashMap<>(); + private final AtomicReference<Exception> exceptionWhileProcessingStream = new AtomicReference<>(); private ExecuteWatchdog watchdog = null; ProcessWrapper(@Nullable Path baseDir, Consumer<String> stdOutLineConsumer, Consumer<String> stdErrLineConsumer, Map<String, String> envVariablesOverrides, String... command) { @@ -93,6 +95,9 @@ public class ProcessWrapperFactory { if (exitValue != 0 && !watchdog.killedProcess()) { throw new IllegalStateException(format("Command execution exited with code: %d", exitValue), resultHandler.getException()); } + if (exceptionWhileProcessingStream.get() != null) { + throw new IllegalStateException("Error while processing stream for command", exceptionWhileProcessingStream.get()); + } } catch (InterruptedException e) { LOG.warn("Command [{}] interrupted", join(" ", command), e); Thread.currentThread().interrupt(); @@ -105,17 +110,9 @@ public class ProcessWrapperFactory { builder.setWorkingDirectory(baseDir.toFile()); } - PumpStreamHandler psh = new PumpStreamHandler(new LogOutputStream() { - @Override - protected void processLine(String line, int logLevel) { - stdOutLineConsumer.accept(line); - } - }, new LogOutputStream() { - @Override - protected void processLine(String line, int logLevel) { - stdErrLineConsumer.accept(line); - } - }); + PumpStreamHandler psh = new PumpStreamHandler( + new ExceptionCatchingLogOutputStream(stdOutLineConsumer), + new ExceptionCatchingLogOutputStream(stdErrLineConsumer)); builder.setExecuteStreamHandler(psh); var executor = builder.get(); @@ -127,5 +124,24 @@ public class ProcessWrapperFactory { public void destroy() { watchdog.destroyProcess(); } + + private class ExceptionCatchingLogOutputStream extends LogOutputStream { + + private final Consumer<String> lineConsumer; + + public ExceptionCatchingLogOutputStream(Consumer<String> lineConsumer) { + this.lineConsumer = lineConsumer; + } + + @Override + protected void processLine(String line, int logLevel) { + try { + lineConsumer.accept(line); + } catch (Exception e) { + exceptionWhileProcessingStream.compareAndSet(null, e); + watchdog.destroyProcess(); + } + } + } } } diff --git a/sonar-core/src/test/java/org/sonar/core/util/ProcessWrapperFactoryTest.java b/sonar-core/src/test/java/org/sonar/core/util/ProcessWrapperFactoryTest.java index 1fc0a4205e2..04627cd808c 100644 --- a/sonar-core/src/test/java/org/sonar/core/util/ProcessWrapperFactoryTest.java +++ b/sonar-core/src/test/java/org/sonar/core/util/ProcessWrapperFactoryTest.java @@ -83,6 +83,26 @@ class ProcessWrapperFactoryTest { } @Test + void should_not_deadlock_when_stream_handler_throw_exception(@TempDir Path temp) throws IOException { + var bigFile = temp.resolve("stdout.txt"); + for (int i = 0; i < 1024; i++) { + Files.writeString(bigFile, StringUtils.repeat("a", 1024), StandardCharsets.UTF_8, StandardOpenOption.APPEND, StandardOpenOption.CREATE); + Files.writeString(bigFile, "\n", StandardCharsets.UTF_8, StandardOpenOption.APPEND); + } + + var stdoutHandler = new ThrowExceptionForEveryLine(); + + var processWrapper = underTest.create(temp, stdoutHandler::process, l -> { + }, + SystemUtils.IS_OS_WINDOWS ? new String[] {"cmd.exe", "/c", "type stdout.txt"} : new String[] {"cat", "stdout.txt"}); + + assertThatThrownBy(processWrapper::execute) + .hasMessage("Error while processing stream for command") + .hasCauseInstanceOf(IllegalStateException.class) + .hasStackTraceContaining("Some error"); + } + + @Test void should_apply_env_overrides_on_top_of_parent_env(@TempDir Path temp) throws IOException { ConcurrentLinkedDeque<String> logs = new ConcurrentLinkedDeque<>(); @@ -99,8 +119,8 @@ class ProcessWrapperFactoryTest { } private static class DestroyProcessAfter10Lines { - private ProcessWrapperFactory.ProcessWrapper wrapper; private final AtomicInteger lineCounter = new AtomicInteger(); + private ProcessWrapperFactory.ProcessWrapper wrapper; void process(String line) { if (lineCounter.incrementAndGet() == 10) { @@ -109,4 +129,11 @@ class ProcessWrapperFactoryTest { } } + private static class ThrowExceptionForEveryLine { + + void process(String line) { + throw new IllegalStateException("Some error"); + } + } + } diff --git a/sonar-scanner-engine-shaded/build.gradle b/sonar-scanner-engine-shaded/build.gradle index 7db17d8d133..172c83e79e7 100644 --- a/sonar-scanner-engine-shaded/build.gradle +++ b/sonar-scanner-engine-shaded/build.gradle @@ -10,6 +10,8 @@ dependencies { api project(':sonar-scanner-engine') } +artifactoryPublish.skip = !deployCommunity + jar { manifest { attributes( @@ -19,3 +21,13 @@ jar { ) } } + +// Used by the scanner integration tester +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + artifact shadowJar + } + } +} diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/bootstrap/BootstrapMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/bootstrap/BootstrapMediumIT.java index 63317e362b9..6cfd6ea5f94 100644 --- a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/bootstrap/BootstrapMediumIT.java +++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/bootstrap/BootstrapMediumIT.java @@ -117,7 +117,7 @@ class BootstrapMediumIT { void should_fail_if_invalid_json_input() { var in = new ByteArrayInputStream("}".getBytes()); - var exitCode = ScannerMain.run(in); + var exitCode = ScannerMain.run(in, System.out); assertThat(exitCode).isEqualTo(1); assertThat(logTester.getLogs(Level.ERROR)).hasSize(1); @@ -128,7 +128,7 @@ class BootstrapMediumIT { @Test void should_warn_if_null_property_key() { ScannerMain.run(new ByteArrayInputStream(""" - {"scannerProperties": [{"value": "aValueWithoutKey"}]}""".getBytes())); + {"scannerProperties": [{"value": "aValueWithoutKey"}]}""".getBytes()), System.out); assertThat(logTester.logs(Level.WARN)).contains("Ignoring property with null key. Value='aValueWithoutKey'"); } @@ -136,7 +136,7 @@ class BootstrapMediumIT { @Test void should_warn_if_null_property_value() { ScannerMain.run(new ByteArrayInputStream(""" - {"scannerProperties": [{"key": "aKey", "value": null}]}""".getBytes())); + {"scannerProperties": [{"key": "aKey", "value": null}]}""".getBytes()), System.out); assertThat(logTester.logs(Level.WARN)).contains("Ignoring property with null value. Key='aKey'"); } @@ -144,7 +144,7 @@ class BootstrapMediumIT { @Test void should_warn_if_not_provided_property_value() { ScannerMain.run(new ByteArrayInputStream(""" - {"scannerProperties": [{"key": "aKey"}]}""".getBytes())); + {"scannerProperties": [{"key": "aKey"}]}""".getBytes()), System.out); assertThat(logTester.logs(Level.WARN)).contains("Ignoring property with null value. Key='aKey'"); } @@ -152,7 +152,7 @@ class BootstrapMediumIT { @Test void should_warn_if_duplicate_property_keys() { ScannerMain.run(new ByteArrayInputStream(""" - {"scannerProperties": [{"key": "aKey", "value": "aValue"}, {"key": "aKey", "value": "aValue"}]}""".getBytes())); + {"scannerProperties": [{"key": "aKey", "value": "aValue"}, {"key": "aKey", "value": "aValue"}]}""".getBytes()), System.out); assertThat(logTester.logs(Level.WARN)).contains("Duplicated properties. Key='aKey'"); } @@ -160,7 +160,7 @@ class BootstrapMediumIT { @Test void should_warn_if_null_property() { ScannerMain.run(new ByteArrayInputStream(""" - {"scannerProperties": [{"key": "aKey", "value": "aValue"},]}""".getBytes())); + {"scannerProperties": [{"key": "aKey", "value": "aValue"},]}""".getBytes()), System.out); assertThat(logTester.logs(Level.WARN)).contains("Ignoring null or empty property"); } @@ -168,7 +168,7 @@ class BootstrapMediumIT { @Test void should_warn_if_empty_property() { ScannerMain.run(new ByteArrayInputStream(""" - {"scannerProperties": [{}]}""".getBytes())); + {"scannerProperties": [{}]}""".getBytes()), System.out); assertThat(logTester.logs(Level.WARN)).contains("Ignoring null or empty property"); } @@ -229,7 +229,7 @@ class BootstrapMediumIT { } private int runScannerEngine(ScannerProperties scannerProperties) { - return ScannerMain.run(new ByteArrayInputStream(scannerProperties.toJson().getBytes())); + return ScannerMain.run(new ByteArrayInputStream(scannerProperties.toJson().getBytes()), System.out); } static class ScannerProperties { diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumIT.java index 04eff59c778..babeadf518e 100644 --- a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumIT.java +++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumIT.java @@ -1358,6 +1358,7 @@ class FileSystemMediumIT { .addRules(new XooRulesDefinition()) .addActiveRule("xoo", "OneIssuePerFile", null, "Issue Per File", "MAJOR", null, "xoo") .newAnalysis(new File(projectDir, "sonar-project.properties")) + .property("sonar.exclusions", "**/*.ignore") .property("sonar.oneIssuePerFile.enableHiddenFileProcessing", "true"); if (setHiddenFileScanningExplicitly) { @@ -1385,6 +1386,7 @@ class FileSystemMediumIT { .addRules(new XooRulesDefinition()) .addActiveRule("xoo", "OneIssuePerFile", null, "Issue Per File", "MAJOR", null, "xoo") .newAnalysis(new File(projectDir, "sonar-project.properties")) + .property("sonar.exclusions", "**/*.ignore") .property("sonar.oneIssuePerFile.enableHiddenFileProcessing", "false") .execute(); @@ -1410,6 +1412,7 @@ class FileSystemMediumIT { .addRules(new XooRulesDefinition()) .addActiveRule("xoo", "OneIssuePerFile", null, "Issue Per File", "MAJOR", null, "xoo") .newAnalysis(new File(projectDir, "sonar-project.properties")) + .property("sonar.exclusions", "**/*.ignore") .property("sonar.scanner.excludeHiddenFiles", "true") // hidden files are not scanned, so issues can't be raised on them regardless if the sensor wants to process them .property("sonar.oneIssuePerFile.enableHiddenFileProcessing", String.valueOf(sensorHiddenFileProcessingEnabled)) @@ -1490,6 +1493,46 @@ class FileSystemMediumIT { assertHiddenFileScan(result, "moduleB/src/.xoo", true, true); } + @Test + void shouldScanAndAnalyzeAllHiddenFilesWithRespectToExclusions() throws IOException { + prepareHiddenFileProject(); + File projectDir = new File("test-resources/mediumtest/xoo/sample-with-hidden-files"); + + + AnalysisResult result = tester + .addRules(new XooRulesDefinition()) + .addActiveRule("xoo", "OneIssuePerFile", null, "Issue Per File", "MAJOR", null, "xoo") + .newAnalysis(new File(projectDir, "sonar-project.properties")) + .property("sonar.scm.provider", "xoo") + .property("sonar.oneIssuePerFile.enableHiddenFileProcessing", "true") + .property("sonar.exclusions", "**/.nestedHidden/**,**/*.ignore") + .execute(); + + Set<String> excludedFiles = Set.of( + // sonar.exclusions + "xources/.hidden/.nestedHidden/.xoo", + "xources/.hidden/.nestedHidden/Class.xoo", + "xources/.hidden/.nestedHidden/visibleInHiddenFolder/.xoo", + "xources/.hidden/.nestedHidden/visibleInHiddenFolder/.xoo.ignore", + "xources/.hidden/.nestedHidden/visibleInHiddenFolder/Class.xoo", + // scm ignore + "xources/nonHidden/.hiddenInVisibleFolder/.xoo"); + + for (Map.Entry<String, Boolean> pathToHiddenStatus : hiddenFileProjectExpectedHiddenStatus().entrySet()) { + String filePath = pathToHiddenStatus.getKey(); + boolean expectedIsHidden = pathToHiddenStatus.getValue(); + + if (excludedFiles.contains(filePath)) { + assertThat(result.inputFile(filePath)).isNull(); + } else { + assertHiddenFileScan(result, filePath, expectedIsHidden, true); + // we expect the sensor to process all non-excluded files, regardless of visibility + assertFileIssue(result, filePath, true); + } + } + assertThat(result.inputFiles()).hasSize(5); + } + private File createModuleWithSubdirectory(String moduleName, String subDirName) { File moduleBaseDir = new File(baseDir, moduleName); File srcDir = moduleBaseDir.toPath().resolve(subDirName).toFile(); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/JGitCleanupService.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/JGitCleanupService.java new file mode 100644 index 00000000000..28e052cfa4e --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/JGitCleanupService.java @@ -0,0 +1,47 @@ +/* + * SonarQube + * Copyright (C) 2009-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 org.sonar.scanner.bootstrap; + +import java.lang.reflect.Method; +import org.eclipse.jgit.internal.util.CleanupService; + +/** + * Normally, JGit terminates with a shutdown hook. Since we also want to support running the Scanner Engine in the same JVM, this allows triggering shutdown manually. + */ +class JGitCleanupService implements AutoCloseable { + + private final Method shutDownMethod; + private final CleanupService cleanupService; + + public JGitCleanupService() { + cleanupService = new CleanupService(); + try { + shutDownMethod = CleanupService.class.getDeclaredMethod("shutDown"); + } catch (NoSuchMethodException e) { + throw new IllegalStateException("Unable to find method 'shutDown' on JGit CleanupService", e); + } + shutDownMethod.setAccessible(true); + } + + @Override + public void close() throws Exception { + shutDownMethod.invoke(cleanupService); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerMain.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerMain.java index 7e27a45e4cf..bd8d5b9b99c 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerMain.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerMain.java @@ -21,11 +21,14 @@ package org.sonar.scanner.bootstrap; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.OutputStreamAppender; import com.google.gson.Gson; import com.google.gson.annotations.SerializedName; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; @@ -50,11 +53,13 @@ public class ScannerMain { private static final String SCANNER_APP_VERSION_KEY = "sonar.scanner.appVersion"; public static void main(String... args) { - System.exit(run(System.in)); + System.exit(run(System.in, System.out)); } - public static int run(InputStream in) { - try { + public static int run(InputStream in, OutputStream out) { + try (var ignored = new JGitCleanupService()) { + configureLogOutput(out); + LOG.info("Starting SonarScanner Engine..."); LOG.atInfo().log(ScannerMain::java); @@ -71,6 +76,8 @@ public class ScannerMain { } catch (Throwable throwable) { handleException(throwable); return 1; + } finally { + stopLogback(); } } @@ -156,6 +163,28 @@ public class ScannerMain { rootLogger.setLevel(Level.toLevel(verbose ? LEVEL_ROOT_VERBOSE : LEVEL_ROOT_DEFAULT)); } + private static void configureLogOutput(OutputStream out) { + var loggerContext = (ch.qos.logback.classic.LoggerContext) LoggerFactory.getILoggerFactory(); + var encoder = new ScannerLogbackEncoder(); + encoder.setContext(loggerContext); + encoder.start(); + + var appender = new OutputStreamAppender<ILoggingEvent>(); + appender.setEncoder(encoder); + appender.setContext(loggerContext); + appender.setOutputStream(out); + appender.start(); + + var rootLogger = (Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); + rootLogger.addAppender(appender); + rootLogger.setLevel(Level.toLevel(LEVEL_ROOT_DEFAULT)); + } + + private static void stopLogback() { + var loggerContext = (ch.qos.logback.classic.LoggerContext) LoggerFactory.getILoggerFactory(); + loggerContext.stop(); + } + private static class Input { @SerializedName("scannerProperties") private List<ScannerProperty> scannerProperties; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FileIndexer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FileIndexer.java index d73924c17e0..0961edbd985 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FileIndexer.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FileIndexer.java @@ -94,8 +94,8 @@ public class FileIndexer { // This should be fast; language should be cached from preprocessing step Language language = langDetection.language(sourceFile, projectRelativePath); - // cached from directory file visitation - boolean isHidden = hiddenFilesProjectData.isMarkedAsHiddenFile(sourceFile, module); + // cached from directory file visitation, after querying the data is removed to reduce memory consumption + boolean isHidden = hiddenFilesProjectData.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(sourceFile, module); DefaultIndexedFile indexedFile = new DefaultIndexedFile( sourceFile, diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/HiddenFilesProjectData.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/HiddenFilesProjectData.java index e2d5f57acae..d779a054455 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/HiddenFilesProjectData.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/HiddenFilesProjectData.java @@ -44,12 +44,16 @@ public class HiddenFilesProjectData { hiddenFilesByModule.computeIfAbsent(module, k -> new HashSet<>()).add(file); } - public boolean isMarkedAsHiddenFile(Path file, DefaultInputModule module) { + /** + * To alleviate additional strain on the memory, we remove the visibility information for <code>hiddenFilesByModule</code> mapdirectly after querying, + * as we don't need it afterward. + */ + public boolean getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(Path file, DefaultInputModule module) { Set<Path> hiddenFilesPerModule = hiddenFilesByModule.get(module); - if (hiddenFilesPerModule == null) { - return false; + if (hiddenFilesPerModule != null) { + return hiddenFilesPerModule.remove(file); } - return hiddenFilesPerModule.contains(file); + return false; } public Path getCachedSonarUserHomePath() throws IOException { diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ProjectFilePreprocessor.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ProjectFilePreprocessor.java index 216f86c4f11..3e7b655589c 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ProjectFilePreprocessor.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ProjectFilePreprocessor.java @@ -160,7 +160,11 @@ public class ProjectFilePreprocessor { processedFiles.addAll(processDirectory(module, moduleConfiguration, moduleExclusionFilters, dirOrFile, type, exclusionCounter)); } else { filePreprocessor.processFile(module, moduleExclusionFilters, dirOrFile, type, exclusionCounter, ignoreCommand) - .ifPresent(processedFiles::add); + .ifPresentOrElse( + processedFiles::add, + // If the file is not processed, we don't need to save visibility data and can remove it + () -> hiddenFilesProjectData.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(dirOrFile, module) + ); } } } catch (IOException e) { @@ -173,8 +177,13 @@ public class ProjectFilePreprocessor { InputFile.Type type, ExclusionCounter exclusionCounter) throws IOException { List<Path> processedFiles = new ArrayList<>(); Files.walkFileTree(path.normalize(), Collections.singleton(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, - new DirectoryFileVisitor(file -> filePreprocessor.processFile(module, moduleExclusionFilters, file, type, exclusionCounter, - ignoreCommand).ifPresent(processedFiles::add), module, moduleConfiguration, moduleExclusionFilters, inputModuleHierarchy, type, hiddenFilesProjectData)); + new DirectoryFileVisitor(file -> filePreprocessor + .processFile(module, moduleExclusionFilters, file, type, exclusionCounter, ignoreCommand) + .ifPresentOrElse( + processedFiles::add, + // If the file is not processed, we don't need to save visibility data and can remove it + () -> hiddenFilesProjectData.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(file, module)), + module, moduleConfiguration, moduleExclusionFilters, inputModuleHierarchy, type, hiddenFilesProjectData)); return processedFiles; } diff --git a/sonar-scanner-engine/src/main/resources/logback.xml b/sonar-scanner-engine/src/main/resources/logback.xml index ccd0dfe09b9..ddc2805f08a 100644 --- a/sonar-scanner-engine/src/main/resources/logback.xml +++ b/sonar-scanner-engine/src/main/resources/logback.xml @@ -1,17 +1,8 @@ <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration> -<!-- This logback configuration is used when the scanner engine is bootstrapped using the SonarScannerCli class. --> +<!-- This logback configuration is used when the scanner engine is bootstrapped using the ScannerMain class. --> <configuration scan="false"> - <import class="ch.qos.logback.core.ConsoleAppender"/> - - <appender name="STDOUT" class="ConsoleAppender"> - <encoder class="org.sonar.scanner.bootstrap.ScannerLogbackEncoder"/> - </appender> - - <root level="info"> - <appender-ref ref="STDOUT"/> - </root> <!-- BeanUtils generate too many DEBUG logs when sonar.verbose is set --> <logger name="org.apache.commons.beanutils.converters" level="WARN"/> @@ -31,4 +22,4 @@ <logger name="nl.altindag.ssl.util.CertificateUtils" level="INFO"/> -</configuration>
\ No newline at end of file +</configuration> diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/HiddenFilesProjectDataTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/HiddenFilesProjectDataTest.java index ad3edf95428..d5a6e4ff843 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/HiddenFilesProjectDataTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/HiddenFilesProjectDataTest.java @@ -73,10 +73,10 @@ public class HiddenFilesProjectDataTest { underTest.markAsHiddenFile(myFile2, inputModule); assertThat(underTest.hiddenFilesByModule).hasSize(1); - assertThat(underTest.isMarkedAsHiddenFile(myFile, inputModule)).isTrue(); - assertThat(underTest.isMarkedAsHiddenFile(myFile2, inputModule)).isTrue(); - assertThat(underTest.isMarkedAsHiddenFile(myFile, secondInputModule)).isFalse(); - assertThat(underTest.isMarkedAsHiddenFile(myFile2, secondInputModule)).isFalse(); + assertThat(underTest.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(myFile, inputModule)).isTrue(); + assertThat(underTest.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(myFile2, inputModule)).isTrue(); + assertThat(underTest.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(myFile, secondInputModule)).isFalse(); + assertThat(underTest.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(myFile2, secondInputModule)).isFalse(); } @Test @@ -87,10 +87,10 @@ public class HiddenFilesProjectDataTest { underTest.markAsHiddenFile(myFile2, secondInputModule); assertThat(underTest.hiddenFilesByModule).hasSize(2); - assertThat(underTest.isMarkedAsHiddenFile(myFile, inputModule)).isTrue(); - assertThat(underTest.isMarkedAsHiddenFile(myFile2, inputModule)).isFalse(); - assertThat(underTest.isMarkedAsHiddenFile(myFile, secondInputModule)).isFalse(); - assertThat(underTest.isMarkedAsHiddenFile(myFile2, secondInputModule)).isTrue(); + assertThat(underTest.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(myFile, inputModule)).isTrue(); + assertThat(underTest.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(myFile2, inputModule)).isFalse(); + assertThat(underTest.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(myFile, secondInputModule)).isFalse(); + assertThat(underTest.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(myFile2, secondInputModule)).isTrue(); } @Test @@ -100,7 +100,7 @@ public class HiddenFilesProjectDataTest { underTest.markAsHiddenFile(myFile, inputModule); assertThat(underTest.hiddenFilesByModule).isNotEmpty(); - assertThat(underTest.isMarkedAsHiddenFile(notMarkedFile, secondInputModule)).isFalse(); + assertThat(underTest.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(notMarkedFile, secondInputModule)).isFalse(); } @Test @@ -117,6 +117,36 @@ public class HiddenFilesProjectDataTest { } @Test + public void shouldRemoveVisibilityAfterQuerying() { + Path myFile = Path.of("myFile"); + Path myFile2 = Path.of("myFile2"); + underTest.markAsHiddenFile(myFile, inputModule); + underTest.markAsHiddenFile(myFile2, inputModule); + + assertThat(underTest.hiddenFilesByModule).hasSize(1); + assertThat(underTest.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(myFile, inputModule)).isTrue(); + assertThat(underTest.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(myFile2, inputModule)).isTrue(); + + assertThat(underTest.hiddenFilesByModule).hasSize(1); + assertThat(underTest.hiddenFilesByModule.get(inputModule)).isEmpty(); + assertThat(underTest.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(myFile, inputModule)).isFalse(); + assertThat(underTest.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(myFile2, inputModule)).isFalse(); + } + + @Test + public void shouldOnlyRemoveModuleIfAllFilesAreRemoved() { + Path myFile = Path.of("myFile"); + Path myFile2 = Path.of("myFile2"); + underTest.markAsHiddenFile(myFile, inputModule); + underTest.markAsHiddenFile(myFile2, inputModule); + + assertThat(underTest.hiddenFilesByModule).hasSize(1); + assertThat(underTest.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(myFile, inputModule)).isTrue(); + + assertThat(underTest.hiddenFilesByModule).isNotEmpty(); + } + + @Test public void shouldNotFailOnUserPathResolving() throws IOException { Path expectedPath = sonarUserHome.getPath().toRealPath(LinkOption.NOFOLLOW_LINKS).toAbsolutePath().normalize(); assertThat(underTest.getCachedSonarUserHomePath()).isEqualTo(expectedPath); diff --git a/sonar-scanner-engine/test-resources/mediumtest/xoo/sample-with-hidden-files/xources/nonHidden/.hiddenInVisibleFolder/.xoo.ignore b/sonar-scanner-engine/test-resources/mediumtest/xoo/sample-with-hidden-files/xources/nonHidden/.hiddenInVisibleFolder/.xoo.ignore new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/sonar-scanner-engine/test-resources/mediumtest/xoo/sample-with-hidden-files/xources/nonHidden/.hiddenInVisibleFolder/.xoo.ignore |