import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.apache.ibatis.session.ResultHandler;
return mapper(dbSession).selectDescendants(query, componentOpt.get().uuid(), query.getUuidPath(component));
}
+ public List<ComponentDto> selectChildren(DbSession dbSession, Collection<ComponentDto> components) {
+ Set<String> uuidPaths = components.stream().map(c -> c.getUuidPath() + c.uuid() + ".").collect(Collectors.toSet());
+ return mapper(dbSession).selectChildren(uuidPaths);
+ }
+
public ComponentDto selectOrFailByKey(DbSession session, String key) {
Optional<ComponentDto> component = selectByKey(session, key);
if (!component.isPresent()) {
List<ComponentDto> selectDescendants(@Param("query") ComponentTreeQuery query, @Param("baseUuid") String baseUuid, @Param("baseUuidPath") String baseUuidPath);
+ List<ComponentDto> selectChildren(@Param("uuidPaths") Set<String> uuidPaths);
+
/**
* Returns all enabled projects (Scope {@link org.sonar.api.resources.Scopes#PROJECT} and qualifier
* {@link org.sonar.api.resources.Qualifiers#PROJECT}) no matter if they are ghost project, provisioned projects or
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.db.issue;
+
+public class HotspotGroupDto {
+ private String status;
+ private long count;
+ private boolean inLeak;
+
+ public String getStatus() {
+ return status;
+ }
+
+ public HotspotGroupDto setStatus(String status) {
+ this.status = status;
+ return this;
+ }
+
+ public long getCount() {
+ return count;
+ }
+
+ public HotspotGroupDto setCount(long count) {
+ this.count = count;
+ return this;
+ }
+
+ public boolean isInLeak() {
+ return inLeak;
+ }
+
+ public HotspotGroupDto setInLeak(boolean inLeak) {
+ this.inLeak = inLeak;
+ return this;
+ }
+}
return executeLargeInputs(componentUuids, mapper(dbSession)::selectOpenByComponentUuids);
}
- public Collection<IssueGroupDto> selectIssueGroupsByBaseComponent(DbSession dbSession, ComponentDto baseComponent, long leakPeriodBeginningDate) {
- return mapper(dbSession).selectIssueGroupsByBaseComponent(baseComponent, leakPeriodBeginningDate);
+ public Collection<HotspotGroupDto> selectBranchHotspotsCount(DbSession dbSession, String branchUuid, long leakPeriodBeginningDate) {
+ return mapper(dbSession).selectBranchHotspotsCount(branchUuid, leakPeriodBeginningDate);
+ }
+
+ public Collection<IssueGroupDto> selectIssueGroupsByComponent(DbSession dbSession, ComponentDto component, long leakPeriodBeginningDate) {
+ return mapper(dbSession).selectIssueGroupsByComponent(component, leakPeriodBeginningDate);
}
public void insert(DbSession session, IssueDto dto) {
List<IssueDto> selectNonClosedByModuleOrProject(@Param("projectUuid") String projectUuid, @Param("likeModuleUuidPath") String likeModuleUuidPath);
- Collection<IssueGroupDto> selectIssueGroupsByBaseComponent(
- @Param("baseComponent") ComponentDto baseComponent,
- @Param("leakPeriodBeginningDate") long leakPeriodBeginningDate);
+ Collection<HotspotGroupDto> selectBranchHotspotsCount(@Param("rootUuid") String rootUuid, @Param("leakPeriodBeginningDate") long leakPeriodBeginningDate);
+
+ Collection<IssueGroupDto> selectIssueGroupsByComponent(@Param("component") ComponentDto component, @Param("leakPeriodBeginningDate") long leakPeriodBeginningDate);
List<IssueDto> selectByBranch(@Param("queryParams") IssueQueryParams issueQueryParams,
</if>
</sql>
+ <select id="selectChildren" resultType="Component">
+ select
+ <include refid="componentColumns"/>
+ from components p
+ where p.uuid_path in
+ <foreach collection="uuidPaths" item="uuidPath" open="(" close=")" separator=",">
+ #{uuidPath,jdbcType=VARCHAR}
+ </foreach>
+ and p.enabled = ${_true}
+ </select>
+
<select id="selectDescendants" resultType="Component">
select
<include refid="componentColumns"/>
i.issue_type <> 4
</select>
- <select id="selectIssueGroupsByBaseComponent" resultType="org.sonar.db.issue.IssueGroupDto" parameterType="map">
+ <select id="selectBranchHotspotsCount" resultType="org.sonar.db.issue.HotspotGroupDto" parameterType="map">
+ select i.status as status, count(i.status) as "count",
+ <if test="leakPeriodBeginningDate >= 0">
+ (i.issue_creation_date > #{leakPeriodBeginningDate,jdbcType=BIGINT}) as inLeak
+ </if>
+ <if test="leakPeriodBeginningDate < 0">
+ CASE WHEN n.uuid is null THEN false ELSE true END as inLeak
+ </if>
+ from issues i
+ <if test="leakPeriodBeginningDate < 0">
+ left join new_code_reference_issues n on n.issue_key = i.kee
+ </if>
+ where i.project_uuid = #{rootUuid,jdbcType=VARCHAR}
+ and i.status !='CLOSED'
+ and i.issue_type = 4
+ group by i.status, inLeak
+ </select>
+
+ <select id="selectBranchHotspotsCount" resultType="org.sonar.db.issue.HotspotGroupDto" parameterType="map" databaseId="oracle">
+ select i2.status as status, count(i2.status) as "count", i2.inLeak as inLeak
+ from (
+ select i.status,
+ <if test="leakPeriodBeginningDate >= 0">
+ case when i.issue_creation_date > #{leakPeriodBeginningDate,jdbcType=BIGINT} then 1 else 0 end as inLeak
+ </if>
+ <if test="leakPeriodBeginningDate < 0">
+ case when n.uuid is null then 0 else 1 end as inLeak
+ </if>
+ from issues i
+ <if test="leakPeriodBeginningDate < 0">
+ left join new_code_reference_issues n on n.issue_key = i.kee
+ </if>
+ where i.project_uuid = #{rootUuid,jdbcType=VARCHAR}
+ and i.status !='CLOSED'
+ and i.issue_type = 4
+ ) i2
+ group by i2.status, i2.inLeak
+ </select>
+
+ <select id="selectBranchHotspotsCount" resultType="org.sonar.db.issue.HotspotGroupDto" parameterType="map" databaseId="mssql">
+ select i2.status as status, count(i2.status) as "count", i2.inLeak as inLeak
+ from (
+ select i.status,
+ <if test="leakPeriodBeginningDate >= 0">
+ case when i.issue_creation_date > #{leakPeriodBeginningDate,jdbcType=BIGINT} then 1 else 0 end as inLeak
+ </if>
+ <if test="leakPeriodBeginningDate < 0">
+ case when n.uuid is null then 0 else 1 end as inLeak
+ </if>
+ from issues i
+ <if test="leakPeriodBeginningDate < 0">
+ left join new_code_reference_issues n on n.issue_key = i.kee
+ </if>
+ where i.project_uuid = #{rootUuid,jdbcType=VARCHAR}
+ and i.status !='CLOSED'
+ and i.issue_type = 4
+ ) i2
+ group by i2.status, i2.inLeak
+ </select>
+
+ <select id="selectIssueGroupsByComponent" resultType="org.sonar.db.issue.IssueGroupDto" parameterType="map">
select i.issue_type as ruleType, i.severity as severity, i.resolution as resolution, i.status as status, sum(i.effort) as effort, count(i.issue_type) as "count",
<if test="leakPeriodBeginningDate >= 0">
(i.issue_creation_date > #{leakPeriodBeginningDate,jdbcType=BIGINT}) as inLeak
CASE WHEN n.uuid is null THEN false ELSE true END as inLeak
</if>
from issues i
- inner join components p on p.uuid = i.component_uuid and p.project_uuid = i.project_uuid
- left join new_code_reference_issues n on n.issue_key = i.kee
+ <if test="leakPeriodBeginningDate < 0">
+ left join new_code_reference_issues n on n.issue_key = i.kee
+ </if>
where i.status !='CLOSED'
- and i.project_uuid = #{baseComponent.projectUuid,jdbcType=VARCHAR}
- and (p.uuid_path like #{baseComponent.uuidPathLikeIncludingSelf,jdbcType=VARCHAR} escape '/' or p.uuid = #{baseComponent.uuid,jdbcType=VARCHAR})
+ and i.component_uuid = #{component.uuid,jdbcType=VARCHAR}
group by i.issue_type, i.severity, i.resolution, i.status, inLeak
</select>
- <select id="selectIssueGroupsByBaseComponent" resultType="org.sonar.db.issue.IssueGroupDto" parameterType="map" databaseId="oracle">
+ <select id="selectIssueGroupsByComponent" resultType="org.sonar.db.issue.IssueGroupDto" parameterType="map" databaseId="oracle">
select i2.issue_type as ruleType, i2.severity as severity, i2.resolution as resolution, i2.status as status, sum(i2.effort) as effort, count(i2.issue_type) as "count", i2.inLeak as inLeak
from (
select i.issue_type, i.severity, i.resolution, i.status, i.effort,
- <if test="leakPeriodBeginningDate >= 0">
+ <if test="leakPeriodBeginningDate >= 0">
case when i.issue_creation_date > #{leakPeriodBeginningDate,jdbcType=BIGINT} then 1 else 0 end as inLeak
- </if>
- <if test="leakPeriodBeginningDate < 0">
- case when n.uuid is null then 0 else 1 end as inLeak
- </if>
+ </if>
+ <if test="leakPeriodBeginningDate < 0">
+ case when n.uuid is null then 0 else 1 end as inLeak
+ </if>
from issues i
- inner join components p on p.uuid = i.component_uuid and p.project_uuid = i.project_uuid
- left join new_code_reference_issues n on n.issue_key = i.kee
+ <if test="leakPeriodBeginningDate < 0">
+ left join new_code_reference_issues n on n.issue_key = i.kee
+ </if>
where i.status !='CLOSED'
- and i.project_uuid = #{baseComponent.projectUuid,jdbcType=VARCHAR}
- and (p.uuid_path like #{baseComponent.uuidPathLikeIncludingSelf,jdbcType=VARCHAR} escape '/' or p.uuid = #{baseComponent.uuid,jdbcType=VARCHAR})
+ and i.component_uuid = #{component.uuid,jdbcType=VARCHAR}
) i2
group by i2.issue_type, i2.severity, i2.resolution, i2.status, i2.inLeak
</select>
- <select id="selectIssueGroupsByBaseComponent" resultType="org.sonar.db.issue.IssueGroupDto" parameterType="map" databaseId="mssql">
+ <select id="selectIssueGroupsByComponent" resultType="org.sonar.db.issue.IssueGroupDto" parameterType="map" databaseId="mssql">
select i2.issue_type as ruleType, i2.severity as severity, i2.resolution as resolution, i2.status as status, sum(i2.effort) as effort, count(i2.issue_type) as "count", i2.inLeak as inLeak
from (
- select i.issue_type, i.severity, i.resolution, i.status, i.effort,
- <if test="leakPeriodBeginningDate >= 0">
- case when i.issue_creation_date > #{leakPeriodBeginningDate,jdbcType=BIGINT} then 1 else 0 end as inLeak
- </if>
- <if test="leakPeriodBeginningDate < 0">
- case when n.uuid is null then 0 else 1 end as inLeak
- </if>
- from issues i
- inner join components p on p.uuid = i.component_uuid and p.project_uuid = i.project_uuid
- left join new_code_reference_issues n on n.issue_key = i.kee
- where i.status !='CLOSED'
- and i.project_uuid = #{baseComponent.projectUuid,jdbcType=VARCHAR}
- and (p.uuid_path like #{baseComponent.uuidPathLikeIncludingSelf,jdbcType=VARCHAR} escape '/' or p.uuid = #{baseComponent.uuid,jdbcType=VARCHAR})
+ select i.issue_type, i.severity, i.resolution, i.status, i.effort,
+ <if test="leakPeriodBeginningDate >= 0">
+ case when i.issue_creation_date > #{leakPeriodBeginningDate,jdbcType=BIGINT} then 1 else 0 end as inLeak
+ </if>
+ <if test="leakPeriodBeginningDate < 0">
+ case when n.uuid is null then 0 else 1 end as inLeak
+ </if>
+ from issues i
+ <if test="leakPeriodBeginningDate < 0">
+ left join new_code_reference_issues n on n.issue_key = i.kee
+ </if>
+ where i.status !='CLOSED'
+ and i.component_uuid = #{component.uuid,jdbcType=VARCHAR}
) i2
group by i2.issue_type, i2.severity, i2.resolution, i2.status, i2.inLeak
</select>
left join new_code_reference_issues n on i.kee = n.issue_key
</select>
+
<sql id="selectByBranchColumnsFinal">
result.kee as kee,
result.ruleUuid as ruleUuid,
assertThat(ancestors).extracting("uuid").containsExactly(PROJECT_UUID, MODULE_UUID);
}
+ @Test
+ public void select_children() {
+ ComponentDto project = newPrivateProjectDto(PROJECT_UUID);
+ db.components().insertProjectAndSnapshot(project);
+ ComponentDto module = newModuleDto(MODULE_UUID, project);
+ db.components().insertComponent(module);
+ ComponentDto fileInProject = newFileDto(project, null, FILE_1_UUID).setDbKey("file-key-1").setName("File One");
+ db.components().insertComponent(fileInProject);
+ ComponentDto file1InModule = newFileDto(module, null, FILE_2_UUID).setDbKey("file-key-2").setName("File Two");
+ db.components().insertComponent(file1InModule);
+ ComponentDto file2InModule = newFileDto(module, null, FILE_3_UUID).setDbKey("file-key-3").setName("File Three");
+ db.components().insertComponent(file2InModule);
+ db.commit();
+
+ // test children of root
+ assertThat(underTest.selectChildren(dbSession, List.of(project))).extracting("uuid").containsOnly(FILE_1_UUID, MODULE_UUID);
+
+ // test children of intermediate component (module here)
+ assertThat(underTest.selectChildren(dbSession, List.of(module))).extracting("uuid").containsOnly(FILE_2_UUID, FILE_3_UUID);
+
+ // test children of leaf component (file here)
+ assertThat(underTest.selectChildren(dbSession, List.of(fileInProject))).isEmpty();
+
+ // test children of 2 components
+ assertThat(underTest.selectChildren(dbSession, List.of(project, module))).extracting("uuid").containsOnly(FILE_1_UUID, MODULE_UUID, FILE_2_UUID, FILE_3_UUID);
+ }
+
@Test
public void select_descendants_with_children_strategy() {
// project has 2 children: module and file 1. Other files are part of module.
private static final RuleDto RULE = RuleTesting.newXooX1();
private static final String ISSUE_KEY1 = "I1";
private static final String ISSUE_KEY2 = "I2";
- private static final String DEFAULT_BRANCH_NAME = "master";
private static final RuleType[] RULE_TYPES_EXCEPT_HOTSPOT = Stream.of(RuleType.values())
.filter(r -> r != RuleType.SECURITY_HOTSPOT)
assertThat(underTest.selectNonClosedByComponentUuidExcludingExternalsAndSecurityHotspots(db.getSession(), file.uuid()))
.extracting(IssueDto::getKey)
- .containsExactlyInAnyOrder(Arrays.stream(new IssueDto[]{openIssue1OnFile, openIssue2OnFile}).map(IssueDto::getKey).toArray(String[]::new));
+ .containsExactlyInAnyOrder(Arrays.stream(new IssueDto[] {openIssue1OnFile, openIssue2OnFile}).map(IssueDto::getKey).toArray(String[]::new));
assertThat(underTest.selectNonClosedByComponentUuidExcludingExternalsAndSecurityHotspots(db.getSession(), project.uuid()))
.extracting(IssueDto::getKey)
- .containsExactlyInAnyOrder(Arrays.stream(new IssueDto[]{openIssueOnProject}).map(IssueDto::getKey).toArray(String[]::new));
+ .containsExactlyInAnyOrder(Arrays.stream(new IssueDto[] {openIssueOnProject}).map(IssueDto::getKey).toArray(String[]::new));
assertThat(underTest.selectNonClosedByComponentUuidExcludingExternalsAndSecurityHotspots(db.getSession(), "does_not_exist")).isEmpty();
}
assertThat(underTest.selectNonClosedByModuleOrProjectExcludingExternalsAndSecurityHotspots(db.getSession(), project))
.extracting(IssueDto::getKey)
.containsExactlyInAnyOrder(
- Arrays.stream(new IssueDto[]{openIssue1OnFile, openIssue2OnFile, openIssueOnModule, openIssueOnProject}).map(IssueDto::getKey).toArray(String[]::new));
+ Arrays.stream(new IssueDto[] {openIssue1OnFile, openIssue2OnFile, openIssueOnModule, openIssueOnProject}).map(IssueDto::getKey).toArray(String[]::new));
assertThat(underTest.selectNonClosedByModuleOrProjectExcludingExternalsAndSecurityHotspots(db.getSession(), module))
.extracting(IssueDto::getKey)
- .containsExactlyInAnyOrder(Arrays.stream(new IssueDto[]{openIssue1OnFile, openIssue2OnFile, openIssueOnModule}).map(IssueDto::getKey).toArray(String[]::new));
+ .containsExactlyInAnyOrder(Arrays.stream(new IssueDto[] {openIssue1OnFile, openIssue2OnFile, openIssueOnModule}).map(IssueDto::getKey).toArray(String[]::new));
ComponentDto notPersisted = ComponentTesting.newPrivateProjectDto();
assertThat(underTest.selectNonClosedByModuleOrProjectExcludingExternalsAndSecurityHotspots(db.getSession(), notPersisted)).isEmpty();
}
@Test
- public void test_selectGroupsOfComponentTreeOnLeak_on_component_without_issues() {
+ public void test_selectIssueGroupsByComponent_on_component_without_issues() {
ComponentDto project = db.components().insertPublicProject();
ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
- Collection<IssueGroupDto> groups = underTest.selectIssueGroupsByBaseComponent(db.getSession(), file, 1_000L);
+ Collection<IssueGroupDto> groups = underTest.selectIssueGroupsByComponent(db.getSession(), file, 1_000L);
+
+ assertThat(groups).isEmpty();
+ }
+
+ @Test
+ public void test_selectBranchHotspotsCount_on_component_without_issues() {
+ ComponentDto project = db.components().insertPublicProject();
+ ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
+
+ Collection<HotspotGroupDto> groups = underTest.selectBranchHotspotsCount(db.getSession(), project.uuid(), 1_000L);
assertThat(groups).isEmpty();
}
}
@Test
- public void selectGroupsOfComponentTreeOnLeak_on_file() {
+ public void selectIssueGroupsByComponent_on_file() {
ComponentDto project = db.components().insertPublicProject();
ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
RuleDto rule = db.rules().insert();
IssueDto closed = db.issues().insert(rule, project, file,
i -> i.setStatus("CLOSED").setResolution("REMOVED").setSeverity("CRITICAL").setType(RuleType.BUG).setIssueCreationTime(1_700L));
- Collection<IssueGroupDto> result = underTest.selectIssueGroupsByBaseComponent(db.getSession(), file, 1_000L);
+ Collection<IssueGroupDto> result = underTest.selectIssueGroupsByComponent(db.getSession(), file, 1_000L);
assertThat(result.stream().mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3);
assertThat(result.stream().filter(g -> "FALSE-POSITIVE".equals(g.getResolution())).mapToLong(IssueGroupDto::getCount).sum()).isOne();
assertThat(result.stream().filter(g -> g.getResolution() == null).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(2);
- assertThat(result.stream().filter(g -> g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3);
+ assertThat(result.stream().filter(IssueGroupDto::isInLeak).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3);
assertThat(result.stream().filter(g -> !g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isZero();
// test leak
- result = underTest.selectIssueGroupsByBaseComponent(db.getSession(), file, 999_999_999L);
- assertThat(result.stream().filter(g -> g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isZero();
+ result = underTest.selectIssueGroupsByComponent(db.getSession(), file, 999_999_999L);
+ assertThat(result.stream().filter(IssueGroupDto::isInLeak).mapToLong(IssueGroupDto::getCount).sum()).isZero();
assertThat(result.stream().filter(g -> !g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3);
// test leak using exact creation time of criticalBug2 issue
- result = underTest.selectIssueGroupsByBaseComponent(db.getSession(), file, criticalBug2.getIssueCreationTime());
- assertThat(result.stream().filter(g -> g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isZero();
+ result = underTest.selectIssueGroupsByComponent(db.getSession(), file, criticalBug2.getIssueCreationTime());
+ assertThat(result.stream().filter(IssueGroupDto::isInLeak).mapToLong(IssueGroupDto::getCount).sum()).isZero();
assertThat(result.stream().filter(g -> !g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3);
}
ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
RuleDto rule = db.rules().insert();
IssueDto fpBug = db.issues().insert(rule, project, file,
- i -> i.setStatus("RESOLVED").setResolution("FALSE-POSITIVE").setSeverity("MAJOR").setType(RuleType.BUG));
+ i -> i.setStatus("RESOLVED").setResolution("FALSE-POSITIVE").setSeverity("MAJOR").setType(RuleType.BUG));
IssueDto criticalBug1 = db.issues().insert(rule, project, file,
- i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG));
+ i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG));
IssueDto criticalBug2 = db.issues().insert(rule, project, file,
- i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG));
+ i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG));
db.issues().insert(rule, project, file,
- i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG));
+ i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG));
//two issues part of new code period on reference branch
db.issues().insertNewCodeReferenceIssue(fpBug);
db.issues().insertNewCodeReferenceIssue(criticalBug1);
db.issues().insertNewCodeReferenceIssue(criticalBug2);
- Collection<IssueGroupDto> result = underTest.selectIssueGroupsByBaseComponent(db.getSession(), file, -1);
+ Collection<IssueGroupDto> result = underTest.selectIssueGroupsByComponent(db.getSession(), file, -1);
assertThat(result.stream().mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(4);
assertThat(result.stream().filter(g -> !g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isOne();
}
+ @Test
+ public void selectBranchHotspotsCount_on_project() {
+ ComponentDto project = db.components().insertPublicProject();
+ ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
+ RuleDto rule = db.rules().insert();
+ IssueDto i1 = db.issues().insert(rule, project, file,
+ i -> i.setStatus("REVIEWED").setResolution("SAFE").setSeverity("CRITICAL").setType(RuleType.SECURITY_HOTSPOT).setIssueCreationTime(1_500L));
+ IssueDto i2 = db.issues().insert(rule, project, file,
+ i -> i.setStatus("TO_REVIEW").setResolution(null).setSeverity("CRITICAL").setType(RuleType.SECURITY_HOTSPOT).setIssueCreationTime(1_600L));
+ IssueDto i3 = db.issues().insert(rule, project, file,
+ i -> i.setStatus("TO_REVIEW").setResolution(null).setSeverity("CRITICAL").setType(RuleType.SECURITY_HOTSPOT).setIssueCreationTime(1_700L));
+
+ // closed issues or other types are ignored
+ IssueDto closed = db.issues().insert(rule, project, file,
+ i -> i.setStatus("CLOSED").setResolution("REMOVED").setSeverity("CRITICAL").setType(RuleType.BUG).setIssueCreationTime(1_700L));
+ IssueDto bug = db.issues().insert(rule, project, file,
+ i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG).setIssueCreationTime(1_700L));
+
+ Collection<HotspotGroupDto> result = underTest.selectBranchHotspotsCount(db.getSession(), project.uuid(), 1_000L);
+
+ assertThat(result.stream().mapToLong(HotspotGroupDto::getCount).sum()).isEqualTo(3);
+
+ assertThat(result.stream().filter(g -> g.getStatus().equals("TO_REVIEW")).mapToLong(HotspotGroupDto::getCount).sum()).isEqualTo(2);
+ assertThat(result.stream().filter(g -> g.getStatus().equals("REVIEWED")).mapToLong(HotspotGroupDto::getCount).sum()).isOne();
+ assertThat(result.stream().filter(g -> g.getStatus().equals("CLOSED")).mapToLong(HotspotGroupDto::getCount).sum()).isZero();
+
+ assertThat(result.stream().filter(HotspotGroupDto::isInLeak).mapToLong(HotspotGroupDto::getCount).sum()).isEqualTo(3);
+ assertThat(result.stream().filter(g -> !g.isInLeak()).mapToLong(HotspotGroupDto::getCount).sum()).isZero();
+
+ // test leak
+ result = underTest.selectBranchHotspotsCount(db.getSession(), project.uuid(), 999_999_999L);
+ assertThat(result.stream().filter(HotspotGroupDto::isInLeak).mapToLong(HotspotGroupDto::getCount).sum()).isZero();
+ assertThat(result.stream().filter(g -> !g.isInLeak()).mapToLong(HotspotGroupDto::getCount).sum()).isEqualTo(3);
+ }
+
+ @Test
+ public void selectBranchHotspotsCount_on_project_with_reference_branch() {
+ ComponentDto project = db.components().insertPublicProject();
+ ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
+ RuleDto rule = db.rules().insert();
+ IssueDto i1 = db.issues().insert(rule, project, file,
+ i -> i.setStatus("REVIEWED").setResolution("SAFE").setSeverity("CRITICAL").setType(RuleType.SECURITY_HOTSPOT).setIssueCreationTime(1_500L));
+ IssueDto i2 = db.issues().insert(rule, project, file,
+ i -> i.setStatus("TO_REVIEW").setResolution(null).setSeverity("CRITICAL").setType(RuleType.SECURITY_HOTSPOT).setIssueCreationTime(1_600L));
+ IssueDto i3 = db.issues().insert(rule, project, file,
+ i -> i.setStatus("TO_REVIEW").setResolution(null).setSeverity("CRITICAL").setType(RuleType.SECURITY_HOTSPOT).setIssueCreationTime(1_700L));
+
+ // closed issues or other types are ignored
+ IssueDto closed = db.issues().insert(rule, project, file,
+ i -> i.setStatus("CLOSED").setResolution("REMOVED").setSeverity("CRITICAL").setType(RuleType.BUG).setIssueCreationTime(1_700L));
+ IssueDto bug = db.issues().insert(rule, project, file,
+ i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG).setIssueCreationTime(1_700L));
+
+ db.issues().insertNewCodeReferenceIssue(i1);
+ db.issues().insertNewCodeReferenceIssue(bug);
+
+ Collection<HotspotGroupDto> result = underTest.selectBranchHotspotsCount(db.getSession(), project.uuid(), -1);
+
+ assertThat(result.stream().mapToLong(HotspotGroupDto::getCount).sum()).isEqualTo(3);
+
+ assertThat(result.stream().filter(g -> g.getStatus().equals("TO_REVIEW")).mapToLong(HotspotGroupDto::getCount).sum()).isEqualTo(2);
+ assertThat(result.stream().filter(g -> g.getStatus().equals("REVIEWED")).mapToLong(HotspotGroupDto::getCount).sum()).isOne();
+ assertThat(result.stream().filter(g -> g.getStatus().equals("CLOSED")).mapToLong(HotspotGroupDto::getCount).sum()).isZero();
+
+ assertThat(result.stream().filter(HotspotGroupDto::isInLeak).mapToLong(HotspotGroupDto::getCount).sum()).isEqualTo(1);
+ assertThat(result.stream().filter(g -> !g.isInLeak()).mapToLong(HotspotGroupDto::getCount).sum()).isEqualTo(2);
+ }
+
@Test
public void selectModuleAndDirComponentUuidsOfOpenIssuesForProjectUuid() {
assertThat(underTest.selectModuleAndDirComponentUuidsOfOpenIssuesForProjectUuid(db.getSession(), randomAlphabetic(12)))
assertThat(underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1).isNewCodeReferenceIssue()).isFalse();
}
- @Test
- public void selectByBranch_givenOneIssueOnTheRightBranchAndOneOnTheWrongOne_returnOneIssue() {
- prepareIssuesComponent();
- underTest.insert(db.getSession(), newIssueDto(ISSUE_KEY1)
- .setRuleUuid(RULE.getUuid())
- .setComponentUuid(FILE_UUID)
- .setProjectUuid(PROJECT_UUID));
- underTest.insert(db.getSession(), newIssueDto(ISSUE_KEY2)
- .setRuleUuid(RULE.getUuid())
- .setComponentUuid(FILE_UUID)
- .setProjectUuid("another-branch-uuid"));
- db.getSession().commit();
-
- List<IssueDto> issueDtos = underTest.selectByBranch(db.getSession(),
- new IssueQueryParams(PROJECT_UUID, DEFAULT_BRANCH_NAME, null, null, false, null),
- 1);
-
- assertThat(issueDtos).hasSize(1);
- assertThat(issueDtos.get(0).getKey()).isEqualTo(ISSUE_KEY1);
- }
-
- @Test
- public void selectByBranch_ordersResultByCreationDate() {
- prepareIssuesComponent();
-
- int times = 1;
- for (;times <= 1001; times++) {
- underTest.insert(db.getSession(), newIssueDto(String.valueOf(times))
- .setIssueCreationTime(Long.valueOf(times))
- .setCreatedAt(times)
- .setRuleUuid(RULE.getUuid())
- .setComponentUuid(FILE_UUID)
- .setProjectUuid(PROJECT_UUID));
- }
- // updating time's value to the last actual value that was used for creating an issue
- times--;
- db.getSession().commit();
-
- List<IssueDto> issueDtos = underTest.selectByBranch(db.getSession(),
- new IssueQueryParams(PROJECT_UUID, DEFAULT_BRANCH_NAME, null, null, false, null),
- 2);
-
- assertThat(issueDtos).hasSize(1);
- assertThat(issueDtos.get(0).getKey()).isEqualTo(String.valueOf(times));
- }
-
- @Test
- public void selectByBranch_openIssueNotReturnedWhenResolvedOnlySet() {
- prepareIssuesComponent();
- underTest.insert(db.getSession(), newIssueDto(ISSUE_KEY1)
- .setRuleUuid(RULE.getUuid())
- .setComponentUuid(FILE_UUID)
- .setStatus(Issue.STATUS_OPEN)
- .setProjectUuid(PROJECT_UUID));
- underTest.insert(db.getSession(), newIssueDto(ISSUE_KEY2)
- .setRuleUuid(RULE.getUuid())
- .setComponentUuid(FILE_UUID)
- .setStatus(Issue.STATUS_RESOLVED)
- .setProjectUuid(PROJECT_UUID));
- db.getSession().commit();
-
- List<IssueDto> issueDtos = underTest.selectByBranch(db.getSession(),
- new IssueQueryParams(PROJECT_UUID, DEFAULT_BRANCH_NAME, null, null, true, null),
- 1);
-
- assertThat(issueDtos).hasSize(1);
- assertThat(issueDtos.get(0).getKey()).isEqualTo(ISSUE_KEY2);
- }
-
- @Test
- public void selectRecentlyClosedIssues_doNotReturnIssuesOlderThanTimestamp() {
- prepareIssuesComponent();
- underTest.insert(db.getSession(), newIssueDto(ISSUE_KEY1)
- .setRuleUuid(RULE.getUuid())
- .setComponentUuid(FILE_UUID)
- .setStatus(Issue.STATUS_CLOSED)
- .setIssueUpdateTime(10_000L)
- .setProjectUuid(PROJECT_UUID));
- underTest.insert(db.getSession(), newIssueDto(ISSUE_KEY2)
- .setRuleUuid(RULE.getUuid())
- .setComponentUuid(FILE_UUID)
- .setStatus(Issue.STATUS_CLOSED)
- .setIssueUpdateTime(5_000L)
- .setProjectUuid(PROJECT_UUID));
- db.getSession().commit();
-
- List<String> issueUuids = underTest.selectRecentlyClosedIssues(db.getSession(),
- new IssueQueryParams(PROJECT_UUID, DEFAULT_BRANCH_NAME, null, null, true, 8_000L));
-
- assertThat(issueUuids).hasSize(1);
- assertThat(issueUuids.get(0)).isEqualTo(ISSUE_KEY1);
- }
-
private static IssueDto newIssueDto(String key) {
IssueDto dto = new IssueDto();
dto.setComponent(new ComponentDto().setDbKey("struts:Action").setUuid("component-uuid"));
this.notificationSerializer = notificationSerializer;
}
- public SearchResponseData saveIssueAndPreloadSearchResponseData(DbSession dbSession, DefaultIssue issue,
- IssueChangeContext context, boolean refreshMeasures) {
+ public SearchResponseData saveIssueAndPreloadSearchResponseData(DbSession dbSession, DefaultIssue issue, IssueChangeContext context, boolean refreshMeasures) {
BranchDto branch = getBranch(dbSession, issue, issue.projectUuid());
return saveIssueAndPreloadSearchResponseData(dbSession, issue, context, refreshMeasures, branch);
}
- public SearchResponseData saveIssueAndPreloadSearchResponseData(DbSession dbSession, DefaultIssue issue,
- IssueChangeContext context, boolean refreshMeasures, BranchDto branch) {
-
+ public SearchResponseData saveIssueAndPreloadSearchResponseData(DbSession dbSession, DefaultIssue issue, IssueChangeContext context, boolean refreshMeasures, BranchDto branch) {
Optional<RuleDto> rule = getRuleByKey(dbSession, issue.getRuleKey());
ComponentDto project = dbClient.componentDao().selectOrFailByUuid(dbSession, issue.projectUuid());
ComponentDto component = getComponent(dbSession, issue, issue.componentUuid());
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.server.measure.live;
+
+import java.util.List;
+import java.util.Set;
+import org.sonar.db.component.ComponentDto;
+
+/**
+ * Provides all components needed for the computation of live measures.
+ * The components needed for the computation are:
+ * 1) Components for which issues were modified
+ * 2) All ancestors of 1), up to the root
+ * 3) All immediate children of 1) and 2). The measures in these components won't be recomputed,
+ * but their measures are needed to recompute the measures for components in 1) and 2).
+ */
+public interface ComponentIndex {
+ /**
+ * Immediate children of a component that are relevant for the computation
+ */
+ List<ComponentDto> getChildren(ComponentDto component);
+
+ /**
+ * Uuids of all components relevant for the computation
+ */
+ Set<String> getAllUuids();
+
+ /**
+ * All components that need the measures recalculated, sorted depth first. It corresponds to the points 1) and 2) in the list mentioned in the javadoc of this class.
+ */
+ List<ComponentDto> getSortedTree();
+
+ /**
+ * Branch being recomputed
+ */
+ ComponentDto getBranch();
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.server.measure.live;
+
+import java.util.List;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+
+public class ComponentIndexFactory {
+ private final DbClient dbClient;
+
+ public ComponentIndexFactory(DbClient dbClient) {
+ this.dbClient = dbClient;
+ }
+
+ public ComponentIndex create(DbSession dbSession, List<ComponentDto> touchedComponents) {
+ ComponentIndexImpl idx = new ComponentIndexImpl(dbClient);
+ idx.load(dbSession, touchedComponents);
+ return idx;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.server.measure.live;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+
+import static java.util.Collections.emptyList;
+
+public class ComponentIndexImpl implements ComponentIndex {
+ private final DbClient dbClient;
+ private ComponentDto branchComponent;
+ private List<ComponentDto> sortedComponentsToRoot;
+ private Map<String, List<ComponentDto>> children;
+
+ public ComponentIndexImpl(DbClient dbClient) {
+ this.dbClient = dbClient;
+ }
+
+ /**
+ * Loads all the components required for the calculation of the new values of the live measures based on what components were modified:
+ * - All components between the touched components and the roots of their component trees
+ * - All immediate children of those components
+ */
+ public void load(DbSession dbSession, List<ComponentDto> touchedComponents) {
+ sortedComponentsToRoot = loadTreeOfComponents(dbSession, touchedComponents);
+ branchComponent = findBranchComponent(sortedComponentsToRoot);
+ children = new HashMap<>();
+ List<ComponentDto> childComponents = loadChildren(dbSession, sortedComponentsToRoot);
+ for (ComponentDto c : childComponents) {
+ List<String> uuidPathAsList = c.getUuidPathAsList();
+ String parentUuid = uuidPathAsList.get(uuidPathAsList.size() - 1);
+ children.computeIfAbsent(parentUuid, uuid -> new LinkedList<>()).add(c);
+ }
+ }
+
+ private static ComponentDto findBranchComponent(Collection<ComponentDto> components) {
+ return components.stream().filter(ComponentDto::isRootProject).findFirst()
+ .orElseThrow(() -> new IllegalStateException("No project found in " + components));
+ }
+
+ private List<ComponentDto> loadChildren(DbSession dbSession, Collection<ComponentDto> components) {
+ return dbClient.componentDao().selectChildren(dbSession, components);
+ }
+
+ private List<ComponentDto> loadTreeOfComponents(DbSession dbSession, List<ComponentDto> touchedComponents) {
+ Set<String> componentUuids = new HashSet<>();
+ for (ComponentDto component : touchedComponents) {
+ componentUuids.add(component.uuid());
+ // ancestors, excluding self
+ componentUuids.addAll(component.getUuidPathAsList());
+ }
+ return dbClient.componentDao().selectByUuids(dbSession, componentUuids).stream()
+ .sorted(Comparator.comparing(ComponentDto::getUuidPath).reversed())
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List<ComponentDto> getChildren(ComponentDto component) {
+ return children.getOrDefault(component.uuid(), emptyList());
+ }
+
+ @Override
+ public Set<String> getAllUuids() {
+ Set<String> all = new HashSet<>();
+ sortedComponentsToRoot.forEach(c -> all.add(c.uuid()));
+ for (Collection<ComponentDto> l : children.values()) {
+ for (ComponentDto c : l) {
+ all.add(c.uuid());
+ }
+ }
+ return all;
+ }
+
+ @Override
+ public List<ComponentDto> getSortedTree() {
+ return sortedComponentsToRoot;
+ }
+
+ @Override public ComponentDto getBranch() {
+ return branchComponent;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.server.measure.live;
+
+import java.util.Optional;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+
+import static org.sonar.server.security.SecurityReviewRating.computePercent;
+import static org.sonar.server.security.SecurityReviewRating.computeRating;
+
+public class HotspotMeasureUpdater {
+ private final DbClient dbClient;
+
+ public HotspotMeasureUpdater(DbClient dbClient) {
+ this.dbClient = dbClient;
+ }
+ public void apply(DbSession dbSession, MeasureMatrix matrix, ComponentIndex components, boolean useLeakFormulas, long beginningOfLeak) {
+ HotspotsCounter hotspotsCounter = new HotspotsCounter(dbClient.issueDao().selectBranchHotspotsCount(dbSession, components.getBranch().uuid(), beginningOfLeak));
+ ComponentDto branch = components.getBranch();
+
+ long reviewed = hotspotsCounter.countHotspotsByStatus(Issue.STATUS_REVIEWED, false);
+ long toReview = hotspotsCounter.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, false);
+ matrix.setValue(branch, CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY, reviewed);
+ matrix.setValue(branch, CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY, toReview);
+ Optional<Double> percent = computePercent(toReview, reviewed);
+ percent.ifPresent(p -> matrix.setValue(branch, CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_KEY, p));
+ matrix.setValue(branch, CoreMetrics.SECURITY_REVIEW_RATING_KEY, computeRating(percent.orElse(null)));
+
+ if (useLeakFormulas) {
+ reviewed = hotspotsCounter.countHotspotsByStatus(Issue.STATUS_REVIEWED, true);
+ toReview = hotspotsCounter.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, true);
+ matrix.setLeakValue(branch, CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY, reviewed);
+ matrix.setLeakValue(branch, CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY, toReview);
+ percent = computePercent(toReview, reviewed);
+ percent.ifPresent(p -> matrix.setLeakValue(branch, CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_KEY, p));
+ matrix.setLeakValue(branch, CoreMetrics.NEW_SECURITY_REVIEW_RATING_KEY, computeRating(percent.orElse(null)));
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.server.measure.live;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.sonar.db.issue.HotspotGroupDto;
+
+public class HotspotsCounter {
+ private final Map<String, Count> hotspotsByStatus = new HashMap<>();
+
+ HotspotsCounter(Collection<HotspotGroupDto> groups) {
+ for (HotspotGroupDto group : groups) {
+ if (group.getStatus() != null) {
+ hotspotsByStatus
+ .computeIfAbsent(group.getStatus(), k -> new Count())
+ .add(group);
+ }
+ }
+ }
+
+ public long countHotspotsByStatus(String status, boolean onlyInLeak) {
+ return value(hotspotsByStatus.get(status), onlyInLeak);
+ }
+
+ private static long value(@Nullable Count count, boolean onlyInLeak) {
+ if (count == null) {
+ return 0;
+ }
+ return onlyInLeak ? count.leak : count.absolute;
+ }
+
+ private static class Count {
+ private long absolute = 0L;
+ private long leak = 0L;
+
+ void add(HotspotGroupDto group) {
+ absolute += group.getCount();
+ if (group.isInLeak()) {
+ leak += group.getCount();
+ }
+ }
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.server.measure.live;
-
-import java.util.Collection;
-import java.util.Optional;
-import java.util.function.BiConsumer;
-import org.sonar.api.measures.Metric;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.server.measure.DebtRatingGrid;
-import org.sonar.server.measure.Rating;
-
-import static java.util.Collections.emptyList;
-
-class IssueMetricFormula {
-
- private final Metric metric;
- private final boolean onLeak;
- private final BiConsumer<Context, IssueCounter> formula;
- private final Collection<Metric> dependentMetrics;
-
- IssueMetricFormula(Metric metric, boolean onLeak, BiConsumer<Context, IssueCounter> formula) {
- this(metric, onLeak, formula, emptyList());
- }
-
- IssueMetricFormula(Metric metric, boolean onLeak, BiConsumer<Context, IssueCounter> formula, Collection<Metric> dependentMetrics) {
- this.metric = metric;
- this.onLeak = onLeak;
- this.formula = formula;
- this.dependentMetrics = dependentMetrics;
- }
-
- Metric getMetric() {
- return metric;
- }
-
- boolean isOnLeak() {
- return onLeak;
- }
-
- Collection<Metric> getDependentMetrics() {
- return dependentMetrics;
- }
-
- void compute(Context context, IssueCounter issues) {
- formula.accept(context, issues);
- }
-
- interface Context {
- ComponentDto getComponent();
-
- DebtRatingGrid getDebtRatingGrid();
-
- /**
- * Value that was just refreshed, otherwise value as computed
- * during last analysis.
- * The metric must be declared in the formula dependencies
- * (see {@link IssueMetricFormula#getDependentMetrics()}).
- */
- Optional<Double> getValue(Metric metric);
-
- Optional<Double> getLeakValue(Metric metric);
-
- void setValue(double value);
-
- void setValue(Rating value);
-
- void setLeakValue(double value);
-
- void setLeakValue(Rating value);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.server.measure.live;
-
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.server.ServerSide;
-
-@ServerSide
-public interface IssueMetricFormulaFactory {
- List<IssueMetricFormula> getFormulas();
-
- Set<Metric> getFormulaMetrics();
-
- static Set<Metric> extractMetrics(List<IssueMetricFormula> formulas) {
- return formulas.stream()
- .flatMap(f -> Stream.concat(Stream.of(f.getMetric()), f.getDependentMetrics().stream()))
- .collect(Collectors.toSet());
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.server.measure.live;
-
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.rule.Severity;
-import org.sonar.api.rules.RuleType;
-import org.sonar.server.measure.Rating;
-
-import static java.util.Arrays.asList;
-import static org.sonar.server.measure.Rating.RATING_BY_SEVERITY;
-import static org.sonar.server.security.SecurityReviewRating.computePercent;
-import static org.sonar.server.security.SecurityReviewRating.computeRating;
-
-public class IssueMetricFormulaFactoryImpl implements IssueMetricFormulaFactory {
-
- private static final List<IssueMetricFormula> FORMULAS = asList(
- new IssueMetricFormula(CoreMetrics.CODE_SMELLS, false,
- (context, issues) -> context.setValue(issues.countUnresolvedByType(RuleType.CODE_SMELL, false))),
-
- new IssueMetricFormula(CoreMetrics.BUGS, false,
- (context, issues) -> context.setValue(issues.countUnresolvedByType(RuleType.BUG, false))),
-
- new IssueMetricFormula(CoreMetrics.VULNERABILITIES, false,
- (context, issues) -> context.setValue(issues.countUnresolvedByType(RuleType.VULNERABILITY, false))),
-
- new IssueMetricFormula(CoreMetrics.SECURITY_HOTSPOTS, false,
- (context, issues) -> context.setValue(issues.countUnresolvedByType(RuleType.SECURITY_HOTSPOT, false))),
-
- new IssueMetricFormula(CoreMetrics.VIOLATIONS, false,
- (context, issues) -> context.setValue(issues.countUnresolved(false))),
-
- new IssueMetricFormula(CoreMetrics.BLOCKER_VIOLATIONS, false,
- (context, issues) -> context.setValue(issues.countUnresolvedBySeverity(Severity.BLOCKER, false))),
-
- new IssueMetricFormula(CoreMetrics.CRITICAL_VIOLATIONS, false,
- (context, issues) -> context.setValue(issues.countUnresolvedBySeverity(Severity.CRITICAL, false))),
-
- new IssueMetricFormula(CoreMetrics.MAJOR_VIOLATIONS, false,
- (context, issues) -> context.setValue(issues.countUnresolvedBySeverity(Severity.MAJOR, false))),
-
- new IssueMetricFormula(CoreMetrics.MINOR_VIOLATIONS, false,
- (context, issues) -> context.setValue(issues.countUnresolvedBySeverity(Severity.MINOR, false))),
-
- new IssueMetricFormula(CoreMetrics.INFO_VIOLATIONS, false,
- (context, issues) -> context.setValue(issues.countUnresolvedBySeverity(Severity.INFO, false))),
-
- new IssueMetricFormula(CoreMetrics.FALSE_POSITIVE_ISSUES, false,
- (context, issues) -> context.setValue(issues.countByResolution(Issue.RESOLUTION_FALSE_POSITIVE, false))),
-
- new IssueMetricFormula(CoreMetrics.WONT_FIX_ISSUES, false,
- (context, issues) -> context.setValue(issues.countByResolution(Issue.RESOLUTION_WONT_FIX, false))),
-
- new IssueMetricFormula(CoreMetrics.OPEN_ISSUES, false,
- (context, issues) -> context.setValue(issues.countByStatus(Issue.STATUS_OPEN, false))),
-
- new IssueMetricFormula(CoreMetrics.REOPENED_ISSUES, false,
- (context, issues) -> context.setValue(issues.countByStatus(Issue.STATUS_REOPENED, false))),
-
- new IssueMetricFormula(CoreMetrics.CONFIRMED_ISSUES, false,
- (context, issues) -> context.setValue(issues.countByStatus(Issue.STATUS_CONFIRMED, false))),
-
- new IssueMetricFormula(CoreMetrics.TECHNICAL_DEBT, false,
- (context, issues) -> context.setValue(issues.sumEffortOfUnresolved(RuleType.CODE_SMELL, false))),
-
- new IssueMetricFormula(CoreMetrics.RELIABILITY_REMEDIATION_EFFORT, false,
- (context, issues) -> context.setValue(issues.sumEffortOfUnresolved(RuleType.BUG, false))),
-
- new IssueMetricFormula(CoreMetrics.SECURITY_REMEDIATION_EFFORT, false,
- (context, issues) -> context.setValue(issues.sumEffortOfUnresolved(RuleType.VULNERABILITY, false))),
-
- new IssueMetricFormula(CoreMetrics.SQALE_DEBT_RATIO, false,
- (context, issues) -> context.setValue(100.0 * debtDensity(context)),
- asList(CoreMetrics.TECHNICAL_DEBT, CoreMetrics.DEVELOPMENT_COST)),
-
- new IssueMetricFormula(CoreMetrics.SQALE_RATING, false,
- (context, issues) -> context
- .setValue(context.getDebtRatingGrid().getRatingForDensity(debtDensity(context))),
- asList(CoreMetrics.TECHNICAL_DEBT, CoreMetrics.DEVELOPMENT_COST)),
-
- new IssueMetricFormula(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, false,
- (context, issues) -> context.setValue(effortToReachMaintainabilityRatingA(context)), asList(CoreMetrics.TECHNICAL_DEBT, CoreMetrics.DEVELOPMENT_COST)),
-
- new IssueMetricFormula(CoreMetrics.RELIABILITY_RATING, false,
- (context, issues) -> context.setValue(RATING_BY_SEVERITY.get(issues.getHighestSeverityOfUnresolved(RuleType.BUG, false).orElse(Severity.INFO)))),
-
- new IssueMetricFormula(CoreMetrics.SECURITY_RATING, false,
- (context, issues) -> context.setValue(RATING_BY_SEVERITY.get(issues.getHighestSeverityOfUnresolved(RuleType.VULNERABILITY, false).orElse(Severity.INFO)))),
-
- new IssueMetricFormula(CoreMetrics.SECURITY_REVIEW_RATING, false,
- (context, issues) -> {
- Optional<Double> percent = computePercent(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, false), issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, false));
- context.setValue(computeRating(percent.orElse(null)));
- }),
-
- new IssueMetricFormula(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED, false,
- (context, issues) -> computePercent(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, false), issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, false))
- .ifPresent(context::setValue)),
-
- new IssueMetricFormula(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS, false,
- (context, issues) -> context.setValue(issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, false))),
-
- new IssueMetricFormula(CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS, false,
- (context, issues) -> context.setValue(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, false))),
-
- new IssueMetricFormula(CoreMetrics.NEW_CODE_SMELLS, true,
- (context, issues) -> context.setLeakValue(issues.countUnresolvedByType(RuleType.CODE_SMELL, true))),
-
- new IssueMetricFormula(CoreMetrics.NEW_BUGS, true,
- (context, issues) -> context.setLeakValue(issues.countUnresolvedByType(RuleType.BUG, true))),
-
- new IssueMetricFormula(CoreMetrics.NEW_VULNERABILITIES, true,
- (context, issues) -> context.setLeakValue(issues.countUnresolvedByType(RuleType.VULNERABILITY, true))),
-
- new IssueMetricFormula(CoreMetrics.NEW_SECURITY_HOTSPOTS, true,
- (context, issues) -> context.setLeakValue(issues.countUnresolvedByType(RuleType.SECURITY_HOTSPOT, true))),
-
- new IssueMetricFormula(CoreMetrics.NEW_VIOLATIONS, true,
- (context, issues) -> context.setLeakValue(issues.countUnresolved(true))),
-
- new IssueMetricFormula(CoreMetrics.NEW_BLOCKER_VIOLATIONS, true,
- (context, issues) -> context.setLeakValue(issues.countUnresolvedBySeverity(Severity.BLOCKER, true))),
-
- new IssueMetricFormula(CoreMetrics.NEW_CRITICAL_VIOLATIONS, true,
- (context, issues) -> context.setLeakValue(issues.countUnresolvedBySeverity(Severity.CRITICAL, true))),
-
- new IssueMetricFormula(CoreMetrics.NEW_MAJOR_VIOLATIONS, true,
- (context, issues) -> context.setLeakValue(issues.countUnresolvedBySeverity(Severity.MAJOR, true))),
-
- new IssueMetricFormula(CoreMetrics.NEW_MINOR_VIOLATIONS, true,
- (context, issues) -> context.setLeakValue(issues.countUnresolvedBySeverity(Severity.MINOR, true))),
-
- new IssueMetricFormula(CoreMetrics.NEW_INFO_VIOLATIONS, true,
- (context, issues) -> context.setLeakValue(issues.countUnresolvedBySeverity(Severity.INFO, true))),
-
- new IssueMetricFormula(CoreMetrics.NEW_TECHNICAL_DEBT, true,
- (context, issues) -> context.setLeakValue(issues.sumEffortOfUnresolved(RuleType.CODE_SMELL, true))),
-
- new IssueMetricFormula(CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT, true,
- (context, issues) -> context.setLeakValue(issues.sumEffortOfUnresolved(RuleType.BUG, true))),
-
- new IssueMetricFormula(CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT, true,
- (context, issues) -> context.setLeakValue(issues.sumEffortOfUnresolved(RuleType.VULNERABILITY, true))),
-
- new IssueMetricFormula(CoreMetrics.NEW_RELIABILITY_RATING, true,
- (context, issues) -> {
- String highestSeverity = issues.getHighestSeverityOfUnresolved(RuleType.BUG, true).orElse(Severity.INFO);
- context.setLeakValue(RATING_BY_SEVERITY.get(highestSeverity));
- }),
-
- new IssueMetricFormula(CoreMetrics.NEW_SECURITY_RATING, true,
- (context, issues) -> {
- String highestSeverity = issues.getHighestSeverityOfUnresolved(RuleType.VULNERABILITY, true).orElse(Severity.INFO);
- context.setLeakValue(RATING_BY_SEVERITY.get(highestSeverity));
- }),
-
- new IssueMetricFormula(CoreMetrics.NEW_SECURITY_REVIEW_RATING, true,
- (context, issues) -> {
- Optional<Double> percent = computePercent(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, true), issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, true));
- context.setLeakValue(computeRating(percent.orElse(null)));
- }),
-
- new IssueMetricFormula(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED, true,
- (context, issues) -> computePercent(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, true), issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, true))
- .ifPresent(context::setLeakValue)),
-
- new IssueMetricFormula(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS, true,
- (context, issues) -> context.setLeakValue(issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, true))),
-
- new IssueMetricFormula(CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS, true,
- (context, issues) -> context.setLeakValue(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, true))),
-
- new IssueMetricFormula(CoreMetrics.NEW_SQALE_DEBT_RATIO, true,
- (context, issues) -> context.setLeakValue(100.0 * newDebtDensity(context)),
- asList(CoreMetrics.NEW_TECHNICAL_DEBT, CoreMetrics.NEW_DEVELOPMENT_COST)),
-
- new IssueMetricFormula(CoreMetrics.NEW_MAINTAINABILITY_RATING, true,
- (context, issues) -> context.setLeakValue(context.getDebtRatingGrid().getRatingForDensity(
- newDebtDensity(context))),
- asList(CoreMetrics.NEW_TECHNICAL_DEBT, CoreMetrics.NEW_DEVELOPMENT_COST)));
-
- private static final Set<Metric> FORMULA_METRICS = IssueMetricFormulaFactory.extractMetrics(FORMULAS);
-
- private static double debtDensity(IssueMetricFormula.Context context) {
- double debt = Math.max(context.getValue(CoreMetrics.TECHNICAL_DEBT).orElse(0.0), 0.0);
- Optional<Double> devCost = context.getValue(CoreMetrics.DEVELOPMENT_COST);
- if (devCost.isPresent() && Double.doubleToRawLongBits(devCost.get()) > 0L) {
- return debt / devCost.get();
- }
- return 0.0;
- }
-
- private static double newDebtDensity(IssueMetricFormula.Context context) {
- double debt = Math.max(context.getLeakValue(CoreMetrics.NEW_TECHNICAL_DEBT).orElse(0.0), 0.0);
- Optional<Double> devCost = context.getLeakValue(CoreMetrics.NEW_DEVELOPMENT_COST);
- if (devCost.isPresent() && Double.doubleToRawLongBits(devCost.get()) > 0L) {
- return debt / devCost.get();
- }
- return 0.0;
- }
-
- private static double effortToReachMaintainabilityRatingA(IssueMetricFormula.Context context) {
- double developmentCost = context.getValue(CoreMetrics.DEVELOPMENT_COST).orElse(0.0);
- double effort = context.getValue(CoreMetrics.TECHNICAL_DEBT).orElse(0.0);
- double upperGradeCost = context.getDebtRatingGrid().getGradeLowerBound(Rating.B) * developmentCost;
- return upperGradeCost < effort ? (effort - upperGradeCost) : 0.0;
- }
-
- @Override
- public List<IssueMetricFormula> getFormulas() {
- return FORMULAS;
- }
-
- @Override
- public Set<Metric> getFormulaMetrics() {
- return FORMULA_METRICS;
- }
-}
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import javax.annotation.CheckForNull;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.BranchDto;
-import org.sonar.db.component.BranchType;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.SnapshotDto;
import org.sonar.db.measure.LiveMeasureComparator;
import org.sonar.db.project.ProjectDto;
import org.sonar.server.es.ProjectIndexer;
import org.sonar.server.es.ProjectIndexers;
-import org.sonar.server.measure.DebtRatingGrid;
-import org.sonar.server.measure.Rating;
import org.sonar.server.qualitygate.EvaluatedQualityGate;
import org.sonar.server.qualitygate.QualityGate;
import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
import org.sonar.server.setting.ProjectConfigurationLoader;
-import static com.google.common.base.Preconditions.checkState;
import static java.util.Collections.emptyList;
import static java.util.Collections.singleton;
-import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.groupingBy;
import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
-import static org.sonar.core.util.stream.MoreCollectors.toArrayList;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
-import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH;
public class LiveMeasureComputerImpl implements LiveMeasureComputer {
private final DbClient dbClient;
- private final IssueMetricFormulaFactory formulaFactory;
+ private final MeasureUpdateFormulaFactory formulaFactory;
+ private final ComponentIndexFactory componentIndexFactory;
private final LiveQualityGateComputer qGateComputer;
private final ProjectConfigurationLoader projectConfigurationLoader;
private final ProjectIndexers projectIndexer;
+ private final LiveMeasureTreeUpdater treeUpdater;
- public LiveMeasureComputerImpl(DbClient dbClient, IssueMetricFormulaFactory formulaFactory,
- LiveQualityGateComputer qGateComputer, ProjectConfigurationLoader projectConfigurationLoader, ProjectIndexers projectIndexer) {
+ public LiveMeasureComputerImpl(DbClient dbClient, MeasureUpdateFormulaFactory formulaFactory, ComponentIndexFactory componentIndexFactory,
+ LiveQualityGateComputer qGateComputer, ProjectConfigurationLoader projectConfigurationLoader, ProjectIndexers projectIndexer, LiveMeasureTreeUpdater treeUpdater) {
this.dbClient = dbClient;
this.formulaFactory = formulaFactory;
+ this.componentIndexFactory = componentIndexFactory;
this.qGateComputer = qGateComputer;
this.projectConfigurationLoader = projectConfigurationLoader;
this.projectIndexer = projectIndexer;
+ this.treeUpdater = treeUpdater;
}
@Override
}
private Optional<QGChangeEvent> refreshComponentsOnSameProject(DbSession dbSession, List<ComponentDto> touchedComponents) {
- // load all the components to be refreshed, including their ancestors
- List<ComponentDto> components = loadTreeOfComponents(dbSession, touchedComponents);
- ComponentDto branchComponent = findBranchComponent(components);
- BranchDto branch = loadBranch(dbSession, branchComponent);
- ProjectDto project = loadProject(dbSession, branch.getProjectUuid());
- Optional<SnapshotDto> lastAnalysisResult = dbClient.snapshotDao().selectLastAnalysisByRootComponentUuid(dbSession, branchComponent.uuid());
- if (lastAnalysisResult.isEmpty()) {
+ ComponentIndex components = componentIndexFactory.create(dbSession, touchedComponents);
+ ComponentDto branchComponent = components.getBranch();
+ Optional<SnapshotDto> lastAnalysis = dbClient.snapshotDao().selectLastAnalysisByRootComponentUuid(dbSession, branchComponent.uuid());
+ if (lastAnalysis.isEmpty()) {
return Optional.empty();
}
- var lastAnalysis = lastAnalysisResult.get();
-
- QualityGate qualityGate = qGateComputer.loadQualityGate(dbSession, project, branch);
- Collection<String> metricKeys = getKeysOfAllInvolvedMetrics(qualityGate);
-
- List<MetricDto> metrics = dbClient.metricDao().selectByKeys(dbSession, metricKeys);
- Map<String, MetricDto> metricsPerId = metrics.stream()
- .collect(uniqueIndex(MetricDto::getUuid));
- List<String> componentUuids = components.stream().map(ComponentDto::uuid).collect(toArrayList(components.size()));
- List<LiveMeasureDto> dbMeasures = dbClient.liveMeasureDao().selectByComponentUuidsAndMetricUuids(dbSession, componentUuids, metricsPerId.keySet());
- // previous status must be load now as MeasureMatrix mutate the LiveMeasureDto which are passed to it
- Metric.Level previousStatus = loadPreviousStatus(metrics, dbMeasures);
-
+ BranchDto branch = loadBranch(dbSession, branchComponent);
Configuration config = projectConfigurationLoader.loadProjectConfiguration(dbSession, branchComponent);
- DebtRatingGrid debtRatingGrid = new DebtRatingGrid(config);
-
- MeasureMatrix matrix = new MeasureMatrix(components, metricsPerId.values(), dbMeasures);
- FormulaContextImpl context = new FormulaContextImpl(matrix, debtRatingGrid);
- long beginningOfLeak = getBeginningOfLeakPeriod(lastAnalysis, branch);
+ ProjectDto project = loadProject(dbSession, branch.getProjectUuid());
+ QualityGate qualityGate = qGateComputer.loadQualityGate(dbSession, project, branch);
+ MeasureMatrix matrix = loadMeasureMatrix(dbSession, components.getAllUuids(), qualityGate);
- components.forEach(c -> {
- IssueCounter issueCounter = new IssueCounter(dbClient.issueDao().selectIssueGroupsByBaseComponent(dbSession, c, beginningOfLeak));
- for (IssueMetricFormula formula : formulaFactory.getFormulas()) {
- // use formulas when the leak period is defined, it's a PR, or the formula is not about the leak period
- if (shouldUseLeakFormulas(lastAnalysis, branch) || !formula.isOnLeak()) {
- context.change(c, formula);
- try {
- formula.compute(context, issueCounter);
- } catch (RuntimeException e) {
- throw new IllegalStateException("Fail to compute " + formula.getMetric().getKey() + " on " + context.getComponent().getDbKey(), e);
- }
- }
- }
- });
+ treeUpdater.update(dbSession, lastAnalysis.get(), config, components, branch, matrix);
+ Metric.Level previousStatus = loadPreviousStatus(dbSession, branchComponent);
EvaluatedQualityGate evaluatedQualityGate = qGateComputer.refreshGateStatus(branchComponent, qualityGate, matrix, config);
+ persistAndIndex(dbSession, matrix, branchComponent);
- // persist the measures that have been created or updated
- matrix.getChanged().sorted(LiveMeasureComparator.INSTANCE)
- .forEach(m -> dbClient.liveMeasureDao().insertOrUpdate(dbSession, m));
- projectIndexer.commitAndIndexComponents(dbSession, singleton(branchComponent), ProjectIndexer.Cause.MEASURE_CHANGE);
-
- return Optional.of(
- new QGChangeEvent(project, branch, lastAnalysis, config, previousStatus, () -> Optional.of(evaluatedQualityGate)));
- }
-
- private static long getBeginningOfLeakPeriod(SnapshotDto lastAnalysis, BranchDto branch) {
- if (isPR(branch)) {
- return 0L;
- } else if (REFERENCE_BRANCH.name().equals(lastAnalysis.getPeriodMode())) {
- return -1;
- }
- return ofNullable(lastAnalysis.getPeriodDate())
- .orElse(Long.MAX_VALUE);
-
+ return Optional.of(new QGChangeEvent(project, branch, lastAnalysis.get(), config, previousStatus, () -> Optional.of(evaluatedQualityGate)));
}
- private static boolean isPR(BranchDto branch) {
- return branch.getBranchType() == BranchType.PULL_REQUEST;
+ private MeasureMatrix loadMeasureMatrix(DbSession dbSession, Set<String> componentUuids, QualityGate qualityGate) {
+ Collection<String> metricKeys = getKeysOfAllInvolvedMetrics(qualityGate);
+ Map<String, MetricDto> metricsPerUuid = dbClient.metricDao().selectByKeys(dbSession, metricKeys).stream().collect(uniqueIndex(MetricDto::getUuid));
+ List<LiveMeasureDto> measures = dbClient.liveMeasureDao().selectByComponentUuidsAndMetricUuids(dbSession, componentUuids, metricsPerUuid.keySet());
+ return new MeasureMatrix(componentUuids, metricsPerUuid.values(), measures);
}
- private static boolean shouldUseLeakFormulas(SnapshotDto lastAnalysis, BranchDto branch) {
- return lastAnalysis.getPeriodDate() != null || isPR(branch) || REFERENCE_BRANCH.name().equals(lastAnalysis.getPeriodMode());
+ private void persistAndIndex(DbSession dbSession, MeasureMatrix matrix, ComponentDto branchComponent) {
+ // persist the measures that have been created or updated
+ matrix.getChanged().sorted(LiveMeasureComparator.INSTANCE).forEach(m -> dbClient.liveMeasureDao().insertOrUpdate(dbSession, m));
+ projectIndexer.commitAndIndexComponents(dbSession, singleton(branchComponent), ProjectIndexer.Cause.MEASURE_CHANGE);
}
@CheckForNull
- private static Metric.Level loadPreviousStatus(List<MetricDto> metrics, List<LiveMeasureDto> dbMeasures) {
- MetricDto alertStatusMetric = metrics.stream()
- .filter(m -> ALERT_STATUS_KEY.equals(m.getKey()))
- .findAny()
- .orElseThrow(() -> new IllegalStateException(String.format("Metric with key %s is not registered", ALERT_STATUS_KEY)));
- return dbMeasures.stream()
- .filter(m -> m.getMetricUuid().equals(alertStatusMetric.getUuid()))
- .map(LiveMeasureDto::getTextValue)
- .filter(Objects::nonNull)
- .map(m -> {
- try {
- return Metric.Level.valueOf(m);
- } catch (IllegalArgumentException e) {
- Loggers.get(LiveMeasureComputerImpl.class)
- .trace("Failed to parse value of metric '{}'", m, e);
- return null;
- }
- })
- .filter(Objects::nonNull)
- .findAny()
- .orElse(null);
- }
+ private Metric.Level loadPreviousStatus(DbSession dbSession, ComponentDto branchComponent) {
+ Optional<LiveMeasureDto> measure = dbClient.liveMeasureDao().selectMeasure(dbSession, branchComponent.uuid(), ALERT_STATUS_KEY);
+ if (measure.isEmpty()) {
+ return null;
+ }
- private List<ComponentDto> loadTreeOfComponents(DbSession dbSession, List<ComponentDto> touchedComponents) {
- Set<String> componentUuids = new HashSet<>();
- for (ComponentDto component : touchedComponents) {
- componentUuids.add(component.uuid());
- // ancestors, excluding self
- componentUuids.addAll(component.getUuidPathAsList());
+ try {
+ return Metric.Level.valueOf(measure.get().getTextValue());
+ } catch (IllegalArgumentException e) {
+ Loggers.get(LiveMeasureComputerImpl.class).trace("Failed to parse value of metric '{}'", ALERT_STATUS_KEY, e);
+ return null;
}
- // Contrary to the formulas in Compute Engine,
- // measures do not aggregate values of descendant components.
- // As a consequence nodes do not need to be sorted. Formulas can be applied
- // on components in any order.
- return dbClient.componentDao().selectByUuids(dbSession, componentUuids);
}
private Set<String> getKeysOfAllInvolvedMetrics(QualityGate gate) {
Set<String> metricKeys = new HashSet<>();
- for (Metric metric : formulaFactory.getFormulaMetrics()) {
+ for (Metric<?> metric : formulaFactory.getFormulaMetrics()) {
metricKeys.add(metric.getKey());
}
metricKeys.addAll(qGateComputer.getMetricsRelatedTo(gate));
return metricKeys;
}
- private static ComponentDto findBranchComponent(Collection<ComponentDto> components) {
- return components.stream().filter(ComponentDto::isRootProject).findFirst()
- .orElseThrow(() -> new IllegalStateException("No project found in " + components));
- }
-
private BranchDto loadBranch(DbSession dbSession, ComponentDto branchComponent) {
return dbClient.branchDao().selectByUuid(dbSession, branchComponent.uuid())
.orElseThrow(() -> new IllegalStateException("Branch not found: " + branchComponent.uuid()));
return dbClient.projectDao().selectByUuid(dbSession, uuid)
.orElseThrow(() -> new IllegalStateException("Project not found: " + uuid));
}
-
- private static class FormulaContextImpl implements IssueMetricFormula.Context {
- private final MeasureMatrix matrix;
- private final DebtRatingGrid debtRatingGrid;
- private ComponentDto currentComponent;
- private IssueMetricFormula currentFormula;
-
- private FormulaContextImpl(MeasureMatrix matrix, DebtRatingGrid debtRatingGrid) {
- this.matrix = matrix;
- this.debtRatingGrid = debtRatingGrid;
- }
-
- private void change(ComponentDto component, IssueMetricFormula formula) {
- this.currentComponent = component;
- this.currentFormula = formula;
- }
-
- @Override
- public ComponentDto getComponent() {
- return currentComponent;
- }
-
- @Override
- public DebtRatingGrid getDebtRatingGrid() {
- return debtRatingGrid;
- }
-
- @Override
- public Optional<Double> getValue(Metric metric) {
- Optional<LiveMeasureDto> measure = matrix.getMeasure(currentComponent, metric.getKey());
- return measure.map(LiveMeasureDto::getValue);
- }
-
- @Override
- public Optional<Double> getLeakValue(Metric metric) {
- Optional<LiveMeasureDto> measure = matrix.getMeasure(currentComponent, metric.getKey());
- return measure.map(LiveMeasureDto::getVariation);
- }
-
- @Override
- public void setValue(double value) {
- String metricKey = currentFormula.getMetric().getKey();
- checkState(!currentFormula.isOnLeak(), "Formula of metric %s accepts only leak values", metricKey);
- matrix.setValue(currentComponent, metricKey, value);
- }
-
- @Override
- public void setLeakValue(double value) {
- String metricKey = currentFormula.getMetric().getKey();
- checkState(currentFormula.isOnLeak(), "Formula of metric %s does not accept leak values", metricKey);
- matrix.setLeakValue(currentComponent, metricKey, value);
- }
-
- @Override
- public void setValue(Rating value) {
- String metricKey = currentFormula.getMetric().getKey();
- checkState(!currentFormula.isOnLeak(), "Formula of metric %s accepts only leak values", metricKey);
- matrix.setValue(currentComponent, metricKey, value);
- }
-
- @Override
- public void setLeakValue(Rating value) {
- String metricKey = currentFormula.getMetric().getKey();
- checkState(currentFormula.isOnLeak(), "Formula of metric %s does not accept leak values", metricKey);
- matrix.setLeakValue(currentComponent, metricKey, value);
- }
- }
}
@Override
protected void configureModule() {
add(
- IssueMetricFormulaFactoryImpl.class,
+ MeasureUpdateFormulaFactoryImpl.class,
+ ComponentIndexFactory.class,
+ LiveMeasureTreeUpdaterImpl.class,
LiveMeasureComputerImpl.class,
+ HotspotMeasureUpdater.class,
LiveQualityGateComputerImpl.class);
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.server.measure.live;
+
+import org.sonar.api.config.Configuration;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.SnapshotDto;
+
+public interface LiveMeasureTreeUpdater {
+ void update(DbSession dbSession, SnapshotDto lastAnalysis, Configuration config, ComponentIndex components, BranchDto branch, MeasureMatrix measures);
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.server.measure.live;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.measures.Metric;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.measure.LiveMeasureDto;
+import org.sonar.server.measure.DebtRatingGrid;
+import org.sonar.server.measure.Rating;
+
+import static com.google.common.base.Preconditions.checkState;
+import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH;
+
+public class LiveMeasureTreeUpdaterImpl implements LiveMeasureTreeUpdater {
+ private final DbClient dbClient;
+ private final MeasureUpdateFormulaFactory formulaFactory;
+ private final HotspotMeasureUpdater hotspotMeasureUpdater;
+
+ public LiveMeasureTreeUpdaterImpl(DbClient dbClient, MeasureUpdateFormulaFactory formulaFactory, HotspotMeasureUpdater hotspotMeasureUpdater) {
+ this.dbClient = dbClient;
+ this.formulaFactory = formulaFactory;
+ this.hotspotMeasureUpdater = hotspotMeasureUpdater;
+ }
+
+ @Override
+ public void update(DbSession dbSession, SnapshotDto lastAnalysis, Configuration config, ComponentIndex components, BranchDto branch, MeasureMatrix measures) {
+ long beginningOfLeak = getBeginningOfLeakPeriod(lastAnalysis, branch);
+ boolean shouldUseLeakFormulas = shouldUseLeakFormulas(lastAnalysis, branch);
+
+ // 1. set new measure from issues to each component from touched components to the root
+ updateMatrixWithIssues(dbSession, measures, components, config, shouldUseLeakFormulas, beginningOfLeak);
+
+ // 2. aggregate new measures up the component tree
+ updateMatrixWithHierarchy(measures, components, config, shouldUseLeakFormulas);
+
+ // 3. Count hotspots at root level
+ // this is only necessary because the count of reviewed and to_review hotspots is only saved for the root (not for all components).
+ // For that reason, we can't incrementally generate the new counts up the tree. To have the correct numbers for the root component, we
+ // run this extra step that set the hotspots measures to the root based on the total count of hotspots.
+ hotspotMeasureUpdater.apply(dbSession, measures, components, shouldUseLeakFormulas, beginningOfLeak);
+ }
+
+ private void updateMatrixWithHierarchy(MeasureMatrix matrix, ComponentIndex components, Configuration config, boolean useLeakFormulas) {
+ DebtRatingGrid debtRatingGrid = new DebtRatingGrid(config);
+ FormulaContextImpl context = new FormulaContextImpl(matrix, components, debtRatingGrid);
+ components.getSortedTree().forEach(c -> {
+ for (MeasureUpdateFormula formula : formulaFactory.getFormulas()) {
+ if (useLeakFormulas || !formula.isOnLeak()) {
+ context.change(c, formula);
+ try {
+ formula.computeHierarchy(context);
+ } catch (RuntimeException e) {
+ throw new IllegalStateException("Fail to compute " + formula.getMetric().getKey() + " on " + context.getComponent().getDbKey(), e);
+ }
+ }
+ }
+ });
+ }
+
+ private void updateMatrixWithIssues(DbSession dbSession, MeasureMatrix matrix, ComponentIndex components, Configuration config, boolean useLeakFormulas, long beginningOfLeak) {
+ DebtRatingGrid debtRatingGrid = new DebtRatingGrid(config);
+ FormulaContextImpl context = new FormulaContextImpl(matrix, components, debtRatingGrid);
+
+ components.getSortedTree().forEach(c -> {
+ IssueCounter issueCounter = new IssueCounter(dbClient.issueDao().selectIssueGroupsByComponent(dbSession, c, beginningOfLeak));
+ for (MeasureUpdateFormula formula : formulaFactory.getFormulas()) {
+ // use formulas when the leak period is defined, it's a PR, or the formula is not about the leak period
+ if (useLeakFormulas || !formula.isOnLeak()) {
+ context.change(c, formula);
+ try {
+ formula.compute(context, issueCounter);
+ } catch (RuntimeException e) {
+ throw new IllegalStateException("Fail to compute " + formula.getMetric().getKey() + " on " + context.getComponent().getDbKey(), e);
+ }
+ }
+ }
+ });
+ }
+
+ private static long getBeginningOfLeakPeriod(SnapshotDto lastAnalysis, BranchDto branch) {
+ if (isPR(branch)) {
+ return 0L;
+ } else if (REFERENCE_BRANCH.name().equals(lastAnalysis.getPeriodMode())) {
+ return -1;
+ } else {
+ return Optional.ofNullable(lastAnalysis.getPeriodDate()).orElse(Long.MAX_VALUE);
+ }
+ }
+
+ private static boolean isPR(BranchDto branch) {
+ return branch.getBranchType() == BranchType.PULL_REQUEST;
+ }
+
+ private static boolean shouldUseLeakFormulas(SnapshotDto lastAnalysis, BranchDto branch) {
+ return lastAnalysis.getPeriodDate() != null || isPR(branch) || REFERENCE_BRANCH.name().equals(lastAnalysis.getPeriodMode());
+ }
+
+ public static class FormulaContextImpl implements MeasureUpdateFormula.Context {
+ private final MeasureMatrix matrix;
+ private final ComponentIndex componentIndex;
+ private final DebtRatingGrid debtRatingGrid;
+ private ComponentDto currentComponent;
+ private MeasureUpdateFormula currentFormula;
+
+ public FormulaContextImpl(MeasureMatrix matrix, ComponentIndex componentIndex, DebtRatingGrid debtRatingGrid) {
+ this.matrix = matrix;
+ this.componentIndex = componentIndex;
+ this.debtRatingGrid = debtRatingGrid;
+ }
+
+ private void change(ComponentDto component, MeasureUpdateFormula formula) {
+ this.currentComponent = component;
+ this.currentFormula = formula;
+ }
+
+ public List<Double> getChildrenValues() {
+ List<ComponentDto> children = componentIndex.getChildren(currentComponent);
+ return children.stream()
+ .flatMap(c -> matrix.getMeasure(c, currentFormula.getMetric().getKey()).stream())
+ .map(LiveMeasureDto::getValue)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ public List<Double> getChildrenLeakValues() {
+ List<ComponentDto> children = componentIndex.getChildren(currentComponent);
+ return children.stream()
+ .flatMap(c -> matrix.getMeasure(c, currentFormula.getMetric().getKey()).stream())
+ .map(LiveMeasureDto::getVariation)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public ComponentDto getComponent() {
+ return currentComponent;
+ }
+
+ @Override
+ public DebtRatingGrid getDebtRatingGrid() {
+ return debtRatingGrid;
+ }
+
+ @Override
+ public Optional<Double> getValue(Metric metric) {
+ Optional<LiveMeasureDto> measure = matrix.getMeasure(currentComponent, metric.getKey());
+ return measure.map(LiveMeasureDto::getValue);
+ }
+
+ @Override
+ public Optional<String> getText(Metric metric) {
+ Optional<LiveMeasureDto> measure = matrix.getMeasure(currentComponent, metric.getKey());
+ return measure.map(LiveMeasureDto::getTextValue);
+ }
+
+ @Override
+ public Optional<Double> getLeakValue(Metric metric) {
+ Optional<LiveMeasureDto> measure = matrix.getMeasure(currentComponent, metric.getKey());
+ return measure.map(LiveMeasureDto::getVariation);
+ }
+
+ @Override
+ public void setValue(double value) {
+ String metricKey = currentFormula.getMetric().getKey();
+ checkState(!currentFormula.isOnLeak(), "Formula of metric %s accepts only leak values", metricKey);
+ matrix.setValue(currentComponent, metricKey, value);
+ }
+
+ @Override
+ public void setLeakValue(double value) {
+ String metricKey = currentFormula.getMetric().getKey();
+ checkState(currentFormula.isOnLeak(), "Formula of metric %s does not accept leak values", metricKey);
+ matrix.setLeakValue(currentComponent, metricKey, value);
+ }
+
+ @Override
+ public void setValue(Rating value) {
+ String metricKey = currentFormula.getMetric().getKey();
+ checkState(!currentFormula.isOnLeak(), "Formula of metric %s accepts only leak values", metricKey);
+ matrix.setValue(currentComponent, metricKey, value);
+ }
+
+ @Override
+ public void setLeakValue(Rating value) {
+ String metricKey = currentFormula.getMetric().getKey();
+ checkState(currentFormula.isOnLeak(), "Formula of metric %s does not accept leak values", metricKey);
+ matrix.setLeakValue(currentComponent, metricKey, value);
+ }
+ }
+}
public QualityGate loadQualityGate(DbSession dbSession, ProjectDto project, BranchDto branch) {
QualityGateData qg = qGateFinder.getEffectiveQualityGate(dbSession, project);
Collection<QualityGateConditionDto> conditionDtos = dbClient.gateConditionDao().selectForQualityGate(dbSession, qg.getUuid());
- Set<String> metricUuids = conditionDtos.stream().map(QualityGateConditionDto::getMetricUuid)
- .collect(toHashSet(conditionDtos.size()));
- Map<String, MetricDto> metricsByUuid = dbClient.metricDao().selectByUuids(dbSession, metricUuids).stream()
- .collect(uniqueIndex(MetricDto::getUuid));
+ Set<String> metricUuids = conditionDtos.stream().map(QualityGateConditionDto::getMetricUuid).collect(toHashSet(conditionDtos.size()));
+ Map<String, MetricDto> metricsByUuid = dbClient.metricDao().selectByUuids(dbSession, metricUuids).stream().collect(uniqueIndex(MetricDto::getUuid));
Stream<Condition> conditions = conditionDtos.stream().map(conditionDto -> {
String metricKey = metricsByUuid.get(conditionDto.getMetricUuid()).getKey();
package org.sonar.server.measure.live;
import com.google.common.collect.ArrayTable;
-import com.google.common.collect.Collections2;
import com.google.common.collect.Table;
import java.math.BigDecimal;
import java.math.RoundingMode;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
-import java.util.function.Predicate;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.sonar.db.component.ComponentDto;
* </ul>
*/
class MeasureMatrix {
-
// component uuid -> metric key -> measure
private final Table<String, String, MeasureCell> table;
private final Map<String, MetricDto> metricsByUuids = new HashMap<>();
MeasureMatrix(Collection<ComponentDto> components, Collection<MetricDto> metrics, List<LiveMeasureDto> dbMeasures) {
+ this(components.stream().map(ComponentDto::uuid).collect(Collectors.toSet()), metrics, dbMeasures);
+ }
+
+ MeasureMatrix(Set<String> componentUuids, Collection<MetricDto> metrics, List<LiveMeasureDto> dbMeasures) {
for (MetricDto metric : metrics) {
this.metricsByKeys.put(metric.getKey(), metric);
this.metricsByUuids.put(metric.getUuid(), metric);
}
- this.table = ArrayTable.create(Collections2.transform(components, ComponentDto::uuid), metricsByKeys.keySet());
+ this.table = ArrayTable.create(componentUuids, metricsByKeys.keySet());
+
for (LiveMeasureDto dbMeasure : dbMeasures) {
- table.put(dbMeasure.getComponentUuid(), metricsByUuids.get(dbMeasure.getMetricUuid()).getKey(), new MeasureCell(dbMeasure, false));
+ table.put(dbMeasure.getComponentUuid(), metricsByUuids.get(dbMeasure.getMetricUuid()).getKey(), new MeasureCell(dbMeasure));
}
}
}
void setValue(ComponentDto component, String metricKey, double value) {
- changeCell(component, metricKey, m -> {
- MetricDto metric = getMetric(metricKey);
- double newValue = scale(metric, value);
-
- Double initialValue = m.getValue();
- if (initialValue != null && Double.compare(initialValue, newValue) == 0) {
- return false;
- }
- m.setValue(newValue);
- Double initialVariation = m.getVariation();
- if (initialValue != null && initialVariation != null) {
- double leakInitialValue = initialValue - initialVariation;
- m.setVariation(scale(metric, value - leakInitialValue));
- }
- return true;
- });
+ changeCell(component, metricKey, m -> m.setValue(scale(getMetric(metricKey), value)));
}
void setValue(ComponentDto component, String metricKey, Rating value) {
changeCell(component, metricKey, m -> {
- Double initialValue = m.getValue();
- if (initialValue != null && Double.compare(initialValue, value.getIndex()) == 0) {
- return false;
- }
m.setData(value.name());
m.setValue((double) value.getIndex());
-
- Double initialVariation = m.getVariation();
- if (initialValue != null && initialVariation != null) {
- double leakInitialValue = initialValue - initialVariation;
- m.setVariation(value.getIndex() - leakInitialValue);
- }
- return true;
});
}
void setValue(ComponentDto component, String metricKey, @Nullable String data) {
- changeCell(component, metricKey, m -> {
- if (Objects.equals(m.getDataAsString(), data)) {
- return false;
- }
- m.setData(data);
- return true;
- });
+ changeCell(component, metricKey, m -> m.setData(data));
}
void setLeakValue(ComponentDto component, String metricKey, double variation) {
- changeCell(component, metricKey, c -> {
- double newVariation = scale(getMetric(metricKey), variation);
- if (c.getVariation() != null && Double.compare(c.getVariation(), newVariation) == 0) {
- return false;
- }
- MetricDto metric = metricsByKeys.get(metricKey);
- c.setVariation(scale(metric, variation));
- return true;
- });
+ changeCell(component, metricKey, c -> c.setVariation(scale(metricsByKeys.get(metricKey), variation)));
}
void setLeakValue(ComponentDto component, String metricKey, Rating variation) {
}
Stream<LiveMeasureDto> getChanged() {
- return table.values()
- .stream()
+ return table.values().stream()
.filter(Objects::nonNull)
.filter(MeasureCell::isChanged)
.map(MeasureCell::getMeasure);
}
- private void changeCell(ComponentDto component, String metricKey, Predicate<LiveMeasureDto> changer) {
+ private void changeCell(ComponentDto component, String metricKey, Consumer<LiveMeasureDto> changer) {
MeasureCell cell = table.get(component.uuid(), metricKey);
if (cell == null) {
LiveMeasureDto measure = new LiveMeasureDto()
.setComponentUuid(component.uuid())
.setProjectUuid(component.projectUuid())
.setMetricUuid(metricsByKeys.get(metricKey).getUuid());
- cell = new MeasureCell(measure, true);
+ cell = new MeasureCell(measure);
table.put(component.uuid(), metricKey, cell);
- changer.test(cell.getMeasure());
- } else if (changer.test(cell.getMeasure())) {
- cell.setChanged(true);
}
+ changer.accept(cell.getMeasure());
}
/**
private static class MeasureCell {
private final LiveMeasureDto measure;
- private boolean changed;
+ private final Double initialVariation;
+ private final Double initialValue;
+ private final byte[] initialData;
+ private final String initialTextValue;
- private MeasureCell(LiveMeasureDto measure, boolean changed) {
+ private MeasureCell(LiveMeasureDto measure) {
this.measure = measure;
- this.changed = changed;
+ this.initialValue = measure.getValue();
+ this.initialVariation = measure.getVariation();
+ this.initialData = measure.getData();
+ this.initialTextValue = measure.getTextValue();
}
public LiveMeasureDto getMeasure() {
}
public boolean isChanged() {
- return changed;
- }
-
- public void setChanged(boolean b) {
- this.changed = b;
+ return !Objects.equals(initialValue, measure.getValue()) || !Objects.equals(initialVariation, measure.getVariation())
+ || !Arrays.equals(initialData, measure.getData()) || !Objects.equals(initialTextValue, measure.getTextValue());
}
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.server.measure.live;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+import org.sonar.api.measures.Metric;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.measure.DebtRatingGrid;
+import org.sonar.server.measure.Rating;
+
+import static java.util.Collections.emptyList;
+
+class MeasureUpdateFormula {
+
+ private final Metric metric;
+ private final boolean onLeak;
+ private final BiConsumer<Context, MeasureUpdateFormula> hierarchyFormula;
+ private final BiConsumer<Context, IssueCounter> formula;
+ private final Collection<Metric> dependentMetrics;
+
+ /**
+ * @param hierarchyFormula Called in a second pass through all the components, after 'formula' is called. Used to calculate the aggregate values for each component.
+ * For many metrics, we sum the value of the children to the value of the component
+ * @param formula Used to calculate new values for a metric for each component, based on the issue counts
+ */
+ MeasureUpdateFormula(Metric metric, boolean onLeak, BiConsumer<Context, MeasureUpdateFormula> hierarchyFormula, BiConsumer<Context, IssueCounter> formula) {
+ this(metric, onLeak, hierarchyFormula, formula, emptyList());
+ }
+
+ MeasureUpdateFormula(Metric metric, boolean onLeak, BiConsumer<Context, MeasureUpdateFormula> hierarchyFormula, BiConsumer<Context, IssueCounter> formula,
+ Collection<Metric> dependentMetrics) {
+ this.metric = metric;
+ this.onLeak = onLeak;
+ this.hierarchyFormula = hierarchyFormula;
+ this.formula = formula;
+ this.dependentMetrics = dependentMetrics;
+ }
+
+ Metric getMetric() {
+ return metric;
+ }
+
+ boolean isOnLeak() {
+ return onLeak;
+ }
+
+ Collection<Metric> getDependentMetrics() {
+ return dependentMetrics;
+ }
+
+ void compute(Context context, IssueCounter issues) {
+ formula.accept(context, issues);
+ }
+
+ void computeHierarchy(Context context) {
+ hierarchyFormula.accept(context, this);
+ }
+
+ interface Context {
+ List<Double> getChildrenValues();
+
+ List<Double> getChildrenLeakValues();
+
+ ComponentDto getComponent();
+
+ DebtRatingGrid getDebtRatingGrid();
+
+ /**
+ * Value that was just refreshed, otherwise value as computed
+ * during last analysis.
+ * The metric must be declared in the formula dependencies
+ * (see {@link MeasureUpdateFormula#getDependentMetrics()}).
+ */
+ Optional<Double> getValue(Metric metric);
+
+ Optional<String> getText(Metric metrc);
+
+ Optional<Double> getLeakValue(Metric metric);
+
+ void setValue(double value);
+
+ void setValue(Rating value);
+
+ void setLeakValue(double value);
+
+ void setLeakValue(Rating value);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.server.measure.live;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.server.ServerSide;
+
+@ServerSide
+public interface MeasureUpdateFormulaFactory {
+ List<MeasureUpdateFormula> getFormulas();
+
+ Set<Metric> getFormulaMetrics();
+
+ static Set<Metric> extractMetrics(List<MeasureUpdateFormula> formulas) {
+ return formulas.stream()
+ .flatMap(f -> Stream.concat(Stream.of(f.getMetric()), f.getDependentMetrics().stream()))
+ .collect(Collectors.toSet());
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.server.measure.live;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.OptionalInt;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleType;
+import org.sonar.server.measure.Rating;
+
+import static java.util.Arrays.asList;
+import static org.sonar.api.measures.CoreMetrics.CODE_SMELLS;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS;
+import static org.sonar.server.measure.Rating.RATING_BY_SEVERITY;
+import static org.sonar.server.security.SecurityReviewRating.computePercent;
+import static org.sonar.server.security.SecurityReviewRating.computeRating;
+
+public class MeasureUpdateFormulaFactoryImpl implements MeasureUpdateFormulaFactory {
+ private static final List<MeasureUpdateFormula> FORMULAS = asList(
+ new MeasureUpdateFormula(CODE_SMELLS, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.countUnresolvedByType(RuleType.CODE_SMELL, false))),
+
+ new MeasureUpdateFormula(CoreMetrics.BUGS, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.countUnresolvedByType(RuleType.BUG, false))),
+
+ new MeasureUpdateFormula(CoreMetrics.VULNERABILITIES, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.countUnresolvedByType(RuleType.VULNERABILITY, false))),
+
+ new MeasureUpdateFormula(CoreMetrics.SECURITY_HOTSPOTS, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.countUnresolvedByType(RuleType.SECURITY_HOTSPOT, false))),
+
+ new MeasureUpdateFormula(CoreMetrics.VIOLATIONS, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.countUnresolved(false))),
+
+ new MeasureUpdateFormula(CoreMetrics.BLOCKER_VIOLATIONS, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.countUnresolvedBySeverity(Severity.BLOCKER, false))),
+
+ new MeasureUpdateFormula(CoreMetrics.CRITICAL_VIOLATIONS, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.countUnresolvedBySeverity(Severity.CRITICAL, false))),
+
+ new MeasureUpdateFormula(CoreMetrics.MAJOR_VIOLATIONS, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.countUnresolvedBySeverity(Severity.MAJOR, false))),
+
+ new MeasureUpdateFormula(CoreMetrics.MINOR_VIOLATIONS, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.countUnresolvedBySeverity(Severity.MINOR, false))),
+
+ new MeasureUpdateFormula(CoreMetrics.INFO_VIOLATIONS, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.countUnresolvedBySeverity(Severity.INFO, false))),
+
+ new MeasureUpdateFormula(CoreMetrics.FALSE_POSITIVE_ISSUES, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.countByResolution(Issue.RESOLUTION_FALSE_POSITIVE, false))),
+
+ new MeasureUpdateFormula(CoreMetrics.WONT_FIX_ISSUES, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.countByResolution(Issue.RESOLUTION_WONT_FIX, false))),
+
+ new MeasureUpdateFormula(CoreMetrics.OPEN_ISSUES, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.countByStatus(Issue.STATUS_OPEN, false))),
+
+ new MeasureUpdateFormula(CoreMetrics.REOPENED_ISSUES, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.countByStatus(Issue.STATUS_REOPENED, false))),
+
+ new MeasureUpdateFormula(CoreMetrics.CONFIRMED_ISSUES, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.countByStatus(Issue.STATUS_CONFIRMED, false))),
+
+ new MeasureUpdateFormula(CoreMetrics.TECHNICAL_DEBT, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.sumEffortOfUnresolved(RuleType.CODE_SMELL, false))),
+
+ new MeasureUpdateFormula(CoreMetrics.RELIABILITY_REMEDIATION_EFFORT, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.sumEffortOfUnresolved(RuleType.BUG, false))),
+
+ new MeasureUpdateFormula(CoreMetrics.SECURITY_REMEDIATION_EFFORT, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.sumEffortOfUnresolved(RuleType.VULNERABILITY, false))),
+
+ new MeasureUpdateFormula(CoreMetrics.SQALE_DEBT_RATIO, false,
+ (context, formula) -> context.setValue(100.0 * debtDensity(context)),
+ (context, issues) -> context.setValue(100.0 * debtDensity(context)),
+ asList(CoreMetrics.TECHNICAL_DEBT, CoreMetrics.DEVELOPMENT_COST)),
+
+ new MeasureUpdateFormula(CoreMetrics.SQALE_RATING, false,
+ (context, issues) -> context.setValue(context.getDebtRatingGrid().getRatingForDensity(debtDensity(context))),
+ (context, issues) -> context.setValue(context.getDebtRatingGrid().getRatingForDensity(debtDensity(context))),
+ asList(CoreMetrics.TECHNICAL_DEBT, CoreMetrics.DEVELOPMENT_COST)),
+
+ new MeasureUpdateFormula(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, false,
+ (context, formula) -> context.setValue(effortToReachMaintainabilityRatingA(context)),
+ (context, issues) -> context.setValue(effortToReachMaintainabilityRatingA(context)), asList(CoreMetrics.TECHNICAL_DEBT, CoreMetrics.DEVELOPMENT_COST)),
+
+ new MeasureUpdateFormula(CoreMetrics.RELIABILITY_RATING, false, new MaxRatingChildren(),
+ (context, issues) -> context.setValue(RATING_BY_SEVERITY.get(issues.getHighestSeverityOfUnresolved(RuleType.BUG, false).orElse(Severity.INFO)))),
+
+ new MeasureUpdateFormula(CoreMetrics.SECURITY_RATING, false, new MaxRatingChildren(),
+ (context, issues) -> context.setValue(RATING_BY_SEVERITY.get(issues.getHighestSeverityOfUnresolved(RuleType.VULNERABILITY, false).orElse(Severity.INFO)))),
+
+ new MeasureUpdateFormula(SECURITY_HOTSPOTS_REVIEWED_STATUS, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, false))),
+
+ new MeasureUpdateFormula(SECURITY_HOTSPOTS_TO_REVIEW_STATUS, false, new AddChildren(),
+ (context, issues) -> context.setValue(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, false))),
+
+ new MeasureUpdateFormula(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED, false,
+ (context, formula) -> {
+ Optional<Double> percent = computePercent(
+ context.getValue(SECURITY_HOTSPOTS_TO_REVIEW_STATUS).orElse(0D).longValue(),
+ context.getValue(SECURITY_HOTSPOTS_REVIEWED_STATUS).orElse(0D).longValue());
+ percent.ifPresent(context::setValue);
+ },
+ (context, issues) -> computePercent(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, false), issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, false))
+ .ifPresent(context::setValue)),
+
+ new MeasureUpdateFormula(CoreMetrics.SECURITY_REVIEW_RATING, false,
+ (context, formula) -> context.setValue(computeRating(context.getValue(SECURITY_HOTSPOTS_REVIEWED).orElse(null))),
+ (context, issues) -> {
+ Optional<Double> percent = computePercent(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, false), issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, false));
+ context.setValue(computeRating(percent.orElse(null)));
+ }),
+
+ new MeasureUpdateFormula(CoreMetrics.NEW_CODE_SMELLS, true, new AddChildren(),
+ (context, issues) -> context.setLeakValue(issues.countUnresolvedByType(RuleType.CODE_SMELL, true))),
+
+ new MeasureUpdateFormula(CoreMetrics.NEW_BUGS, true, new AddChildren(),
+ (context, issues) -> context.setLeakValue(issues.countUnresolvedByType(RuleType.BUG, true))),
+
+ new MeasureUpdateFormula(CoreMetrics.NEW_VULNERABILITIES, true, new AddChildren(),
+ (context, issues) -> context.setLeakValue(issues.countUnresolvedByType(RuleType.VULNERABILITY, true))),
+
+ new MeasureUpdateFormula(CoreMetrics.NEW_SECURITY_HOTSPOTS, true, new AddChildren(),
+ (context, issues) -> context.setLeakValue(issues.countUnresolvedByType(RuleType.SECURITY_HOTSPOT, true))),
+
+ new MeasureUpdateFormula(CoreMetrics.NEW_VIOLATIONS, true, new AddChildren(),
+ (context, issues) -> context.setLeakValue(issues.countUnresolved(true))),
+
+ new MeasureUpdateFormula(CoreMetrics.NEW_BLOCKER_VIOLATIONS, true, new AddChildren(),
+ (context, issues) -> context.setLeakValue(issues.countUnresolvedBySeverity(Severity.BLOCKER, true))),
+
+ new MeasureUpdateFormula(CoreMetrics.NEW_CRITICAL_VIOLATIONS, true, new AddChildren(),
+ (context, issues) -> context.setLeakValue(issues.countUnresolvedBySeverity(Severity.CRITICAL, true))),
+
+ new MeasureUpdateFormula(CoreMetrics.NEW_MAJOR_VIOLATIONS, true, new AddChildren(),
+ (context, issues) -> context.setLeakValue(issues.countUnresolvedBySeverity(Severity.MAJOR, true))),
+
+ new MeasureUpdateFormula(CoreMetrics.NEW_MINOR_VIOLATIONS, true, new AddChildren(),
+ (context, issues) -> context.setLeakValue(issues.countUnresolvedBySeverity(Severity.MINOR, true))),
+
+ new MeasureUpdateFormula(CoreMetrics.NEW_INFO_VIOLATIONS, true, new AddChildren(),
+ (context, issues) -> context.setLeakValue(issues.countUnresolvedBySeverity(Severity.INFO, true))),
+
+ new MeasureUpdateFormula(CoreMetrics.NEW_TECHNICAL_DEBT, true, new AddChildren(),
+ (context, issues) -> context.setLeakValue(issues.sumEffortOfUnresolved(RuleType.CODE_SMELL, true))),
+
+ new MeasureUpdateFormula(CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT, true, new AddChildren(),
+ (context, issues) -> context.setLeakValue(issues.sumEffortOfUnresolved(RuleType.BUG, true))),
+
+ new MeasureUpdateFormula(CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT, true, new AddChildren(),
+ (context, issues) -> context.setLeakValue(issues.sumEffortOfUnresolved(RuleType.VULNERABILITY, true))),
+
+ new MeasureUpdateFormula(CoreMetrics.NEW_RELIABILITY_RATING, true, new MaxRatingChildren(),
+ (context, issues) -> {
+ String highestSeverity = issues.getHighestSeverityOfUnresolved(RuleType.BUG, true).orElse(Severity.INFO);
+ context.setLeakValue(RATING_BY_SEVERITY.get(highestSeverity));
+ }),
+
+ new MeasureUpdateFormula(CoreMetrics.NEW_SECURITY_RATING, true, new MaxRatingChildren(),
+ (context, issues) -> {
+ String highestSeverity = issues.getHighestSeverityOfUnresolved(RuleType.VULNERABILITY, true).orElse(Severity.INFO);
+ context.setLeakValue(RATING_BY_SEVERITY.get(highestSeverity));
+ }),
+
+ new MeasureUpdateFormula(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS, true, new AddChildren(),
+ (context, issues) -> context.setLeakValue(issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, true))),
+
+ new MeasureUpdateFormula(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS, true, new AddChildren(),
+ (context, issues) -> context.setLeakValue(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, true))),
+
+ new MeasureUpdateFormula(NEW_SECURITY_HOTSPOTS_REVIEWED, true,
+ (context, formula) -> {
+ Optional<Double> percent = computePercent(
+ context.getLeakValue(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS).orElse(0D).longValue(),
+ context.getLeakValue(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS).orElse(0D).longValue());
+ percent.ifPresent(context::setLeakValue);
+ },
+ (context, issues) -> computePercent(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, true), issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, true))
+ .ifPresent(context::setLeakValue)),
+
+ new MeasureUpdateFormula(CoreMetrics.NEW_SECURITY_REVIEW_RATING, true,
+ (context, formula) -> context.setLeakValue(computeRating(context.getLeakValue(NEW_SECURITY_HOTSPOTS_REVIEWED).orElse(null))),
+ (context, issues) -> {
+ Optional<Double> percent = computePercent(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, true), issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, true));
+ context.setLeakValue(computeRating(percent.orElse(null)));
+ }),
+
+ new MeasureUpdateFormula(CoreMetrics.NEW_SQALE_DEBT_RATIO, true,
+ (context, formula) -> context.setLeakValue(100.0D * newDebtDensity(context)),
+ (context, issues) -> context.setLeakValue(100.0D * newDebtDensity(context)),
+ asList(CoreMetrics.NEW_TECHNICAL_DEBT, CoreMetrics.NEW_DEVELOPMENT_COST)),
+
+ new MeasureUpdateFormula(CoreMetrics.NEW_MAINTAINABILITY_RATING, true,
+ (context, formula) -> context.setLeakValue(context.getDebtRatingGrid().getRatingForDensity(newDebtDensity(context))),
+ (context, issues) -> context.setLeakValue(context.getDebtRatingGrid().getRatingForDensity(newDebtDensity(context))),
+ asList(CoreMetrics.NEW_TECHNICAL_DEBT, CoreMetrics.NEW_DEVELOPMENT_COST)));
+
+ private static final Set<Metric> FORMULA_METRICS = MeasureUpdateFormulaFactory.extractMetrics(FORMULAS);
+
+ private static double debtDensity(MeasureUpdateFormula.Context context) {
+ double debt = Math.max(context.getValue(CoreMetrics.TECHNICAL_DEBT).orElse(0.0D), 0.0D);
+ Optional<Double> devCost = context.getText(CoreMetrics.DEVELOPMENT_COST).map(Double::parseDouble);
+ if (devCost.isPresent() && Double.doubleToRawLongBits(devCost.get()) > 0L) {
+ return debt / devCost.get();
+ }
+ return 0.0D;
+ }
+
+ private static double newDebtDensity(MeasureUpdateFormula.Context context) {
+ double debt = Math.max(context.getLeakValue(CoreMetrics.NEW_TECHNICAL_DEBT).orElse(0.0D), 0.0D);
+ Optional<Double> devCost = context.getText(CoreMetrics.NEW_DEVELOPMENT_COST).map(Double::parseDouble);
+ if (devCost.isPresent() && Double.doubleToRawLongBits(devCost.get()) > 0L) {
+ return debt / devCost.get();
+ }
+ return 0.0D;
+ }
+
+ private static double effortToReachMaintainabilityRatingA(MeasureUpdateFormula.Context context) {
+ double developmentCost = context.getText(CoreMetrics.DEVELOPMENT_COST).map(Double::parseDouble).orElse(0.0D);
+ double effort = context.getValue(CoreMetrics.TECHNICAL_DEBT).orElse(0.0D);
+ double upperGradeCost = context.getDebtRatingGrid().getGradeLowerBound(Rating.B) * developmentCost;
+ return upperGradeCost < effort ? (effort - upperGradeCost) : 0.0D;
+ }
+
+ static class AddChildren implements BiConsumer<MeasureUpdateFormula.Context, MeasureUpdateFormula> {
+ @Override
+ public void accept(MeasureUpdateFormula.Context context, MeasureUpdateFormula formula) {
+ double sum;
+ if (formula.isOnLeak()) {
+ sum = context.getChildrenLeakValues().stream().mapToDouble(x -> x).sum();
+ context.setLeakValue(context.getLeakValue(formula.getMetric()).orElse(0D) + sum);
+ } else {
+ sum = context.getChildrenValues().stream().mapToDouble(x -> x).sum();
+ context.setValue(context.getValue(formula.getMetric()).orElse(0D) + sum);
+ }
+ }
+ }
+
+ private static class MaxRatingChildren implements BiConsumer<MeasureUpdateFormula.Context, MeasureUpdateFormula> {
+ @Override
+ public void accept(MeasureUpdateFormula.Context context, MeasureUpdateFormula formula) {
+ OptionalInt max;
+ if (formula.isOnLeak()) {
+ max = context.getChildrenLeakValues().stream().mapToInt(Double::intValue).max();
+ if (max.isPresent()) {
+ int currentRating = context.getLeakValue(formula.getMetric()).map(Double::intValue).orElse(Rating.A.getIndex());
+ context.setLeakValue(Rating.valueOf(Math.max(currentRating, max.getAsInt())));
+ }
+ } else {
+ max = context.getChildrenValues().stream().mapToInt(Double::intValue).max();
+ if (max.isPresent()) {
+ int currentRating = context.getValue(formula.getMetric()).map(Double::intValue).orElse(Rating.A.getIndex());
+ context.setValue(Rating.valueOf(Math.max(currentRating, max.getAsInt())));
+ }
+ }
+ }
+ }
+
+ @Override
+ public List<MeasureUpdateFormula> getFormulas() {
+ return FORMULAS;
+ }
+
+ @Override
+ public Set<Metric> getFormulaMetrics() {
+ return FORMULA_METRICS;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.server.measure.live;
+
+import java.util.List;
+import org.junit.Test;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ComponentIndexFactoryTest {
+ public DbTester db = DbTester.create();
+ private final ComponentIndexFactory factory = new ComponentIndexFactory(db.getDbClient());
+
+ @Test
+ public void creates_and_loads_instance() {
+ ComponentDto project = db.components().insertPrivateProject();
+ ComponentIndex index = factory.create(db.getSession(), List.of(project));
+
+ assertThat(index.getAllUuids()).containsOnly(project.uuid());
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.server.measure.live;
+
+import java.util.List;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ComponentIndexImplTest {
+ @Rule
+ public DbTester db = DbTester.create();
+ private final ComponentIndexImpl componentIndex = new ComponentIndexImpl(db.getDbClient());
+
+ private ComponentDto project;
+ private ComponentDto dir1;
+ private ComponentDto dir2;
+ private ComponentDto file11;
+ private ComponentDto file12;
+ private ComponentDto file21;
+
+ private ComponentDto branch;
+ private ComponentDto branchDir1;
+ private ComponentDto branchDir2;
+ private ComponentDto branchFile11;
+ private ComponentDto branchFile12;
+ private ComponentDto branchFile21;
+
+ @Before
+ public void setUp() {
+ project = db.components().insertPrivateProject();
+ dir1 = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java"));
+ dir2 = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java2"));
+ file11 = db.components().insertComponent(ComponentTesting.newFileDto(project, dir1));
+ file12 = db.components().insertComponent(ComponentTesting.newFileDto(project, dir1));
+ file21 = db.components().insertComponent(ComponentTesting.newFileDto(project, dir2));
+
+ branch = db.components().insertProjectBranch(project);
+ branchDir1 = db.components().insertComponent(ComponentTesting.newDirectory(branch, "src/main/java"));
+ branchDir2 = db.components().insertComponent(ComponentTesting.newDirectory(branch, "src/main/java2"));
+ branchFile11 = db.components().insertComponent(ComponentTesting.newFileDto(branch, branchDir1));
+ branchFile12 = db.components().insertComponent(ComponentTesting.newFileDto(branch, branchDir1));
+ branchFile21 = db.components().insertComponent(ComponentTesting.newFileDto(branch, branchDir2));
+ }
+
+ @Test
+ public void loads_all_necessary_components() {
+ componentIndex.load(db.getSession(), List.of(file11));
+ assertThat(componentIndex.getSortedTree()).containsExactly(file11, dir1, project);
+ assertThat(componentIndex.getBranch()).isEqualTo(project);
+ assertThat(componentIndex.getAllUuids()).containsOnly(project.uuid(), dir1.uuid(), dir2.uuid(), file11.uuid(), file12.uuid());
+ assertThat(componentIndex.getChildren(dir1)).containsOnly(file11, file12);
+ }
+
+
+ @Test
+ public void loads_all_necessary_components_for_root() {
+ componentIndex.load(db.getSession(), List.of(project));
+ assertThat(componentIndex.getSortedTree()).containsExactly(project);
+ assertThat(componentIndex.getBranch()).isEqualTo(project);
+ assertThat(componentIndex.getAllUuids()).containsOnly(project.uuid(), dir1.uuid(), dir2.uuid());
+ assertThat(componentIndex.getChildren(dir1)).isEmpty();
+ assertThat(componentIndex.getChildren(project)).containsOnly(dir1, dir2);
+ }
+
+ @Test
+ public void loads_all_necessary_components_from_branch() {
+ componentIndex.load(db.getSession(), List.of(branchDir1));
+ assertThat(componentIndex.getSortedTree()).containsExactly(branchDir1, branch);
+ assertThat(componentIndex.getBranch()).isEqualTo(branch);
+ assertThat(componentIndex.getAllUuids()).containsOnly(branch.uuid(), branchDir1.uuid(), branchDir2.uuid(), branchFile11.uuid(), branchFile12.uuid());
+ assertThat(componentIndex.getChildren(branchDir1)).containsOnly(branchFile11, branchFile12);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.server.measure.live;
+
+import java.util.Date;
+import java.util.List;
+import org.assertj.core.data.Offset;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.metric.MetricDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_REVIEW_RATING_KEY;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_KEY;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_REVIEW_RATING_KEY;
+
+public class HotspotMeasureUpdaterTest {
+ @Rule
+ public DbTester db = DbTester.create();
+
+ private final HotspotMeasureUpdater hotspotMeasureUpdater = new HotspotMeasureUpdater(db.getDbClient());
+ private ComponentIndexImpl componentIndex;
+ private MeasureMatrix matrix;
+ private ComponentDto project;
+ private ComponentDto dir;
+ private ComponentDto file1;
+ private ComponentDto file2;
+
+ @Before
+ public void setUp() {
+ // insert project and file structure
+ project = db.components().insertPrivateProject();
+ dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java"));
+ file1 = db.components().insertComponent(ComponentTesting.newFileDto(project, dir));
+ file2 = db.components().insertComponent(ComponentTesting.newFileDto(project, dir));
+
+ // other needed data
+ matrix = new MeasureMatrix(List.of(project, dir, file1, file2), insertHotspotMetrics(), List.of());
+ componentIndex = new ComponentIndexImpl(db.getDbClient());
+ }
+
+ private List<MetricDto> insertHotspotMetrics() {
+ return List.of(
+ db.measures().insertMetric(m -> m.setKey(SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY)),
+ db.measures().insertMetric(m -> m.setKey(SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY)),
+ db.measures().insertMetric(m -> m.setKey(SECURITY_HOTSPOTS_REVIEWED_KEY)),
+ db.measures().insertMetric(m -> m.setKey(SECURITY_REVIEW_RATING_KEY)),
+
+ db.measures().insertMetric(m -> m.setKey(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY)),
+ db.measures().insertMetric(m -> m.setKey(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY)),
+ db.measures().insertMetric(m -> m.setKey(NEW_SECURITY_HOTSPOTS_REVIEWED_KEY)),
+ db.measures().insertMetric(m -> m.setKey(NEW_SECURITY_REVIEW_RATING_KEY))
+ );
+ }
+
+ @Test
+ public void should_count_hotspots_for_root() {
+ db.issues().insertHotspot(project, file1, i -> i.setIssueCreationDate(new Date(999)).setStatus("TO_REVIEW"));
+ db.issues().insertHotspot(project, file1, i -> i.setIssueCreationDate(new Date(999)).setStatus("REVIEWED"));
+
+ db.issues().insertHotspot(project, dir, i -> i.setIssueCreationDate(new Date(1001)).setStatus("TO_REVIEW"));
+ db.issues().insertHotspot(project, file1, i -> i.setIssueCreationDate(new Date(1002)).setStatus("REVIEWED"));
+ db.issues().insertHotspot(project, file1, i -> i.setIssueCreationDate(new Date(1003)).setStatus("REVIEWED"));
+
+ componentIndex.load(db.getSession(), List.of(file1));
+ hotspotMeasureUpdater.apply(db.getSession(), matrix, componentIndex, true, 1000L);
+
+ assertThat(matrix.getMeasure(project, SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY).get().getValue()).isEqualTo(3d);
+ assertThat(matrix.getMeasure(project, SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY).get().getValue()).isEqualTo(2d);
+ assertThat(matrix.getMeasure(project, SECURITY_REVIEW_RATING_KEY).get().getDataAsString()).isEqualTo("C");
+ assertThat(matrix.getMeasure(project, SECURITY_HOTSPOTS_REVIEWED_KEY).get().getValue()).isEqualTo(60d);
+
+ assertThat(matrix.getMeasure(project, NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY).get().getVariation()).isEqualTo(2d);
+ assertThat(matrix.getMeasure(project, NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY).get().getVariation()).isEqualTo(1d);
+ assertThat(matrix.getMeasure(project, NEW_SECURITY_REVIEW_RATING_KEY).get().getVariation()).isEqualTo(3);
+ assertThat(matrix.getMeasure(project, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY).get().getVariation()).isCloseTo(66d, Offset.offset(1d));
+ }
+
+ @Test
+ public void dont_create_leak_measures_if_no_leak_period() {
+ db.issues().insertHotspot(project, dir, i -> i.setIssueCreationDate(new Date(1001)).setStatus("TO_REVIEW"));
+ db.issues().insertHotspot(project, file1, i -> i.setIssueCreationDate(new Date(1002)).setStatus("REVIEWED"));
+ db.issues().insertHotspot(project, file1, i -> i.setIssueCreationDate(new Date(1003)).setStatus("REVIEWED"));
+
+ componentIndex.load(db.getSession(), List.of(file1));
+ hotspotMeasureUpdater.apply(db.getSession(), matrix, componentIndex, false, 1000L);
+
+ assertThat(matrix.getMeasure(project, NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY)).isEmpty();
+ assertThat(matrix.getMeasure(project, NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY)).isEmpty();
+ assertThat(matrix.getMeasure(project, NEW_SECURITY_REVIEW_RATING_KEY)).isEmpty();
+ assertThat(matrix.getMeasure(project, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY)).isEmpty();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.server.measure.live;
+
+import java.util.List;
+import org.junit.Test;
+import org.sonar.db.issue.HotspotGroupDto;
+
+import static java.util.Collections.emptyList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class HotspotsCounterTest {
+ @Test
+ public void counts_hotspots() {
+ HotspotGroupDto group1 = new HotspotGroupDto().setCount(3).setStatus("TO_REVIEW").setInLeak(false);
+ HotspotGroupDto group2 = new HotspotGroupDto().setCount(2).setStatus("REVIEWED").setInLeak(false);
+ HotspotGroupDto group3 = new HotspotGroupDto().setCount(1).setStatus("TO_REVIEW").setInLeak(true);
+ HotspotGroupDto group4 = new HotspotGroupDto().setCount(1).setStatus("REVIEWED").setInLeak(true);
+
+ HotspotsCounter counter = new HotspotsCounter(List.of(group1, group2, group3, group4));
+ assertThat(counter.countHotspotsByStatus("TO_REVIEW", true)).isEqualTo(1);
+ assertThat(counter.countHotspotsByStatus("REVIEWED", true)).isEqualTo(1);
+ assertThat(counter.countHotspotsByStatus("TO_REVIEW", false)).isEqualTo(4);
+ assertThat(counter.countHotspotsByStatus("REVIEWED", false)).isEqualTo(3);
+ }
+
+ @Test
+ public void count_empty_hotspots() {
+ HotspotsCounter counter = new HotspotsCounter(emptyList());
+ assertThat(counter.countHotspotsByStatus("TO_REVIEW", true)).isZero();
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.server.measure.live;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import org.junit.Test;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.rule.Severity;
-import org.sonar.api.rules.RuleType;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.issue.IssueGroupDto;
-import org.sonar.server.measure.DebtRatingGrid;
-import org.sonar.server.measure.Rating;
-
-import static java.util.Arrays.asList;
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class IssueMetricFormulaFactoryImplTest {
-
- private IssueMetricFormulaFactoryImpl underTest = new IssueMetricFormulaFactoryImpl();
-
- @Test
- public void getFormulaMetrics_include_the_dependent_metrics() {
- for (IssueMetricFormula formula : underTest.getFormulas()) {
- assertThat(underTest.getFormulaMetrics()).contains(formula.getMetric());
- for (Metric dependentMetric : formula.getDependentMetrics()) {
- assertThat(underTest.getFormulaMetrics()).contains(dependentMetric);
- }
- }
- }
-
- @Test
- public void test_violations() {
- withNoIssues().assertThatValueIs(CoreMetrics.VIOLATIONS, 0);
- with(newGroup(), newGroup().setCount(4)).assertThatValueIs(CoreMetrics.VIOLATIONS, 5);
-
- // exclude resolved
- IssueGroupDto resolved = newResolvedGroup(Issue.RESOLUTION_FIXED, Issue.STATUS_RESOLVED);
- with(newGroup(), newGroup(), resolved).assertThatValueIs(CoreMetrics.VIOLATIONS, 2);
-
- // include issues on leak
- IssueGroupDto onLeak = newGroup().setCount(11).setInLeak(true);
- with(newGroup(), newGroup(), onLeak).assertThatValueIs(CoreMetrics.VIOLATIONS, 1 + 1 + 11);
- }
-
- @Test
- public void test_bugs() {
- withNoIssues().assertThatValueIs(CoreMetrics.BUGS, 0);
- with(
- newGroup(RuleType.BUG).setSeverity(Severity.MAJOR).setCount(3),
- newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setCount(5),
- // exclude resolved
- newResolvedGroup(RuleType.BUG).setCount(7),
- // not bugs
- newGroup(RuleType.CODE_SMELL).setCount(11))
- .assertThatValueIs(CoreMetrics.BUGS, 3 + 5);
- }
-
- @Test
- public void test_code_smells() {
- withNoIssues().assertThatValueIs(CoreMetrics.CODE_SMELLS, 0);
- with(
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setCount(3),
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setCount(5),
- // exclude resolved
- newResolvedGroup(RuleType.CODE_SMELL).setCount(7),
- // not code smells
- newGroup(RuleType.BUG).setCount(11))
- .assertThatValueIs(CoreMetrics.CODE_SMELLS, 3 + 5);
- }
-
- @Test
- public void test_vulnerabilities() {
- withNoIssues().assertThatValueIs(CoreMetrics.VULNERABILITIES, 0);
- with(
- newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MAJOR).setCount(3),
- newGroup(RuleType.VULNERABILITY).setSeverity(Severity.CRITICAL).setCount(5),
- // exclude resolved
- newResolvedGroup(RuleType.VULNERABILITY).setCount(7),
- // not vulnerabilities
- newGroup(RuleType.BUG).setCount(11))
- .assertThatValueIs(CoreMetrics.VULNERABILITIES, 3 + 5);
- }
-
- @Test
- public void test_security_hotspots() {
- withNoIssues().assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS, 0);
- with(
- newGroup(RuleType.SECURITY_HOTSPOT).setSeverity(Severity.MAJOR).setCount(3),
- newGroup(RuleType.SECURITY_HOTSPOT).setSeverity(Severity.CRITICAL).setCount(5),
- // exclude resolved
- newResolvedGroup(RuleType.SECURITY_HOTSPOT).setCount(7),
- // not hotspots
- newGroup(RuleType.BUG).setCount(11))
- .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS, 3 + 5);
- }
-
- @Test
- public void test_security_review_rating() {
- with(
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3),
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1))
- .assertThatValueIs(CoreMetrics.SECURITY_REVIEW_RATING, Rating.B);
-
- withNoIssues()
- .assertThatValueIs(CoreMetrics.SECURITY_REVIEW_RATING, Rating.A);
- }
-
- @Test
- public void test_security_hotspots_reviewed() {
- with(
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3),
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1))
- .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED, 75.0);
-
- withNoIssues()
- .assertNoValue(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED);
- }
-
- @Test
- public void test_security_hotspots_reviewed_status() {
- with(
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3),
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1))
- .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS, 3.0);
-
- withNoIssues()
- .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS, 0.0);
- }
-
- @Test
- public void test_security_hotspots_to_review_status() {
- with(
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3),
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1))
- .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1.0);
-
- withNoIssues()
- .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 0.0);
- }
-
- @Test
- public void count_unresolved_by_severity() {
- withNoIssues()
- .assertThatValueIs(CoreMetrics.BLOCKER_VIOLATIONS, 0)
- .assertThatValueIs(CoreMetrics.CRITICAL_VIOLATIONS, 0)
- .assertThatValueIs(CoreMetrics.MAJOR_VIOLATIONS, 0)
- .assertThatValueIs(CoreMetrics.MINOR_VIOLATIONS, 0)
- .assertThatValueIs(CoreMetrics.INFO_VIOLATIONS, 0);
-
- with(
- newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MAJOR).setCount(3),
- newGroup(RuleType.BUG).setSeverity(Severity.MAJOR).setCount(5),
- newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setCount(7),
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setCount(11),
- // exclude security hotspot
- newGroup(RuleType.SECURITY_HOTSPOT).setSeverity(Severity.CRITICAL).setCount(15),
- // include leak
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(true).setCount(13),
- // exclude resolved
- newResolvedGroup(RuleType.VULNERABILITY).setSeverity(Severity.INFO).setCount(17),
- newResolvedGroup(RuleType.BUG).setSeverity(Severity.MAJOR).setCount(19),
- newResolvedGroup(RuleType.SECURITY_HOTSPOT).setSeverity(Severity.INFO).setCount(21))
- .assertThatValueIs(CoreMetrics.BLOCKER_VIOLATIONS, 11 + 13)
- .assertThatValueIs(CoreMetrics.CRITICAL_VIOLATIONS, 7)
- .assertThatValueIs(CoreMetrics.MAJOR_VIOLATIONS, 3 + 5)
- .assertThatValueIs(CoreMetrics.MINOR_VIOLATIONS, 0)
- .assertThatValueIs(CoreMetrics.INFO_VIOLATIONS, 0);
- }
-
- @Test
- public void count_resolved() {
- withNoIssues()
- .assertThatValueIs(CoreMetrics.FALSE_POSITIVE_ISSUES, 0)
- .assertThatValueIs(CoreMetrics.WONT_FIX_ISSUES, 0);
-
- with(
- newResolvedGroup(Issue.RESOLUTION_FIXED, Issue.STATUS_RESOLVED).setCount(3),
- newResolvedGroup(Issue.RESOLUTION_FALSE_POSITIVE, Issue.STATUS_CLOSED).setCount(5),
- newResolvedGroup(Issue.RESOLUTION_WONT_FIX, Issue.STATUS_CLOSED).setSeverity(Severity.MAJOR).setCount(7),
- newResolvedGroup(Issue.RESOLUTION_WONT_FIX, Issue.STATUS_CLOSED).setSeverity(Severity.BLOCKER).setCount(11),
- newResolvedGroup(Issue.RESOLUTION_REMOVED, Issue.STATUS_CLOSED).setCount(13),
- // exclude security hotspot
- newResolvedGroup(Issue.RESOLUTION_WONT_FIX, Issue.STATUS_RESOLVED).setCount(15).setRuleType(RuleType.SECURITY_HOTSPOT.getDbConstant()),
- // exclude unresolved
- newGroup(RuleType.VULNERABILITY).setCount(17),
- newGroup(RuleType.BUG).setCount(19))
- .assertThatValueIs(CoreMetrics.FALSE_POSITIVE_ISSUES, 5)
- .assertThatValueIs(CoreMetrics.WONT_FIX_ISSUES, 7 + 11);
- }
-
- @Test
- public void count_by_status() {
- withNoIssues()
- .assertThatValueIs(CoreMetrics.CONFIRMED_ISSUES, 0)
- .assertThatValueIs(CoreMetrics.OPEN_ISSUES, 0)
- .assertThatValueIs(CoreMetrics.REOPENED_ISSUES, 0);
-
- with(
- newGroup().setStatus(Issue.STATUS_CONFIRMED).setSeverity(Severity.BLOCKER).setCount(3),
- newGroup().setStatus(Issue.STATUS_CONFIRMED).setSeverity(Severity.INFO).setCount(5),
- newGroup().setStatus(Issue.STATUS_REOPENED).setCount(7),
- newGroup(RuleType.CODE_SMELL).setStatus(Issue.STATUS_OPEN).setCount(9),
- newGroup(RuleType.BUG).setStatus(Issue.STATUS_OPEN).setCount(11),
- // exclude security hotspot
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN).setCount(12),
- newResolvedGroup(Issue.RESOLUTION_FALSE_POSITIVE, Issue.STATUS_CLOSED).setCount(13))
- .assertThatValueIs(CoreMetrics.CONFIRMED_ISSUES, 3 + 5)
- .assertThatValueIs(CoreMetrics.OPEN_ISSUES, 9 + 11)
- .assertThatValueIs(CoreMetrics.REOPENED_ISSUES, 7);
- }
-
- @Test
- public void test_technical_debt() {
- withNoIssues().assertThatValueIs(CoreMetrics.TECHNICAL_DEBT, 0);
-
- with(
- newGroup(RuleType.CODE_SMELL).setEffort(3.0).setInLeak(false),
- newGroup(RuleType.CODE_SMELL).setEffort(5.0).setInLeak(true),
- // exclude security hotspot
- newGroup(RuleType.SECURITY_HOTSPOT).setEffort(9).setInLeak(true),
- newGroup(RuleType.SECURITY_HOTSPOT).setEffort(11).setInLeak(false),
- // not code smells
- newGroup(RuleType.BUG).setEffort(7.0),
- // exclude resolved
- newResolvedGroup(RuleType.CODE_SMELL).setEffort(17.0))
- .assertThatValueIs(CoreMetrics.TECHNICAL_DEBT, 3.0 + 5.0);
- }
-
- @Test
- public void test_reliability_remediation_effort() {
- withNoIssues().assertThatValueIs(CoreMetrics.RELIABILITY_REMEDIATION_EFFORT, 0);
-
- with(
- newGroup(RuleType.BUG).setEffort(3.0),
- newGroup(RuleType.BUG).setEffort(5.0).setSeverity(Severity.BLOCKER),
- // not bugs
- newGroup(RuleType.CODE_SMELL).setEffort(7.0),
- // exclude resolved
- newResolvedGroup(RuleType.BUG).setEffort(17.0))
- .assertThatValueIs(CoreMetrics.RELIABILITY_REMEDIATION_EFFORT, 3.0 + 5.0);
- }
-
- @Test
- public void test_security_remediation_effort() {
- withNoIssues().assertThatValueIs(CoreMetrics.SECURITY_REMEDIATION_EFFORT, 0);
-
- with(
- newGroup(RuleType.VULNERABILITY).setEffort(3.0),
- newGroup(RuleType.VULNERABILITY).setEffort(5.0).setSeverity(Severity.BLOCKER),
- // not vulnerability
- newGroup(RuleType.CODE_SMELL).setEffort(7.0),
- // exclude resolved
- newResolvedGroup(RuleType.VULNERABILITY).setEffort(17.0))
- .assertThatValueIs(CoreMetrics.SECURITY_REMEDIATION_EFFORT, 3.0 + 5.0);
- }
-
- @Test
- public void test_sqale_debt_ratio_and_sqale_rating() {
- withNoIssues()
- .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0)
- .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
-
- // technical_debt not computed
- with(CoreMetrics.DEVELOPMENT_COST, 0)
- .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0)
- .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
- with(CoreMetrics.DEVELOPMENT_COST, 20)
- .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0)
- .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
-
- // development_cost not computed
- with(CoreMetrics.TECHNICAL_DEBT, 0)
- .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0)
- .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
- with(CoreMetrics.TECHNICAL_DEBT, 20)
- .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0)
- .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
-
- // input measures are available
- with(CoreMetrics.TECHNICAL_DEBT, 20.0)
- .and(CoreMetrics.DEVELOPMENT_COST, 0.0)
- .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0)
- .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
-
- with(CoreMetrics.TECHNICAL_DEBT, 20.0)
- .and(CoreMetrics.DEVELOPMENT_COST, 160.0)
- .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 12.5)
- .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.C);
-
- with(CoreMetrics.TECHNICAL_DEBT, 20.0)
- .and(CoreMetrics.DEVELOPMENT_COST, 10.0)
- .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 200.0)
- .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.E);
-
- // A is 5% --> min debt is exactly 200*0.05=10
- with(CoreMetrics.DEVELOPMENT_COST, 200.0)
- .and(CoreMetrics.TECHNICAL_DEBT, 10.0)
- .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 5.0)
- .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
-
- with(CoreMetrics.TECHNICAL_DEBT, 0.0)
- .and(CoreMetrics.DEVELOPMENT_COST, 0.0)
- .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0)
- .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
-
- with(CoreMetrics.TECHNICAL_DEBT, 0.0)
- .and(CoreMetrics.DEVELOPMENT_COST, 80.0)
- .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0);
-
- with(CoreMetrics.TECHNICAL_DEBT, -20.0)
- .and(CoreMetrics.DEVELOPMENT_COST, 0.0)
- .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0)
- .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
-
- // bug, debt can't be negative
- with(CoreMetrics.TECHNICAL_DEBT, -20.0)
- .and(CoreMetrics.DEVELOPMENT_COST, 80.0)
- .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0)
- .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
-
- // bug, cost can't be negative
- with(CoreMetrics.TECHNICAL_DEBT, 20.0)
- .and(CoreMetrics.DEVELOPMENT_COST, -80.0)
- .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0)
- .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
- }
-
- @Test
- public void test_effort_to_reach_maintainability_rating_A() {
- withNoIssues()
- .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0);
-
- // technical_debt not computed
- with(CoreMetrics.DEVELOPMENT_COST, 0.0)
- .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0);
- with(CoreMetrics.DEVELOPMENT_COST, 20.0)
- .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0);
-
- // development_cost not computed
- with(CoreMetrics.TECHNICAL_DEBT, 0.0)
- .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0);
- with(CoreMetrics.TECHNICAL_DEBT, 20.0)
- // development cost is considered as zero, so the effort is to reach... zero
- .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 20.0);
-
- // B to A
- with(CoreMetrics.DEVELOPMENT_COST, 200.0)
- .and(CoreMetrics.TECHNICAL_DEBT, 40.0)
- // B is 5% --> goal is to reach 200*0.05=10 --> effort is 40-10=30
- .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 40.0 - (200.0 * 0.05));
-
- // E to A
- with(CoreMetrics.DEVELOPMENT_COST, 200.0)
- .and(CoreMetrics.TECHNICAL_DEBT, 180.0)
- // B is 5% --> goal is to reach 200*0.05=10 --> effort is 180-10=170
- .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 180.0 - (200.0 * 0.05));
-
- // already A
- with(CoreMetrics.DEVELOPMENT_COST, 200.0)
- .and(CoreMetrics.TECHNICAL_DEBT, 8.0)
- // B is 5% --> goal is to reach 200*0.05=10 --> debt is already at 8 --> effort to reach A is zero
- .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0);
-
- // exactly lower range of B
- with(CoreMetrics.DEVELOPMENT_COST, 200.0)
- .and(CoreMetrics.TECHNICAL_DEBT, 10.0)
- // B is 5% --> goal is to reach 200*0.05=10 --> debt is 10 --> effort to reach A is zero
- // FIXME need zero to reach A but effective rating is B !
- .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0);
- }
-
- @Test
- public void test_reliability_rating() {
- withNoIssues()
- .assertThatValueIs(CoreMetrics.RELIABILITY_RATING, Rating.A);
-
- with(
- newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setCount(1),
- newGroup(RuleType.BUG).setSeverity(Severity.MINOR).setCount(5),
- // excluded, not a bug
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setCount(3))
- // highest severity of bugs is CRITICAL --> D
- .assertThatValueIs(CoreMetrics.RELIABILITY_RATING, Rating.D);
-
- with(
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setCount(3),
- newGroup(RuleType.VULNERABILITY).setSeverity(Severity.CRITICAL).setCount(5))
- // no bugs --> A
- .assertThatValueIs(CoreMetrics.RELIABILITY_RATING, Rating.A);
- }
-
- @Test
- public void test_security_rating() {
- withNoIssues()
- .assertThatValueIs(CoreMetrics.SECURITY_RATING, Rating.A);
-
- with(
- newGroup(RuleType.VULNERABILITY).setSeverity(Severity.CRITICAL).setCount(1),
- newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MINOR).setCount(5),
- // excluded, not a vulnerability
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setCount(3))
- // highest severity of vulnerabilities is CRITICAL --> D
- .assertThatValueIs(CoreMetrics.SECURITY_RATING, Rating.D);
-
- with(
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setCount(3),
- newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setCount(5))
- // no vulnerabilities --> A
- .assertThatValueIs(CoreMetrics.SECURITY_RATING, Rating.A);
- }
-
- @Test
- public void test_new_bugs() {
- withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_BUGS, 0.0);
-
- with(
- newGroup(RuleType.BUG).setInLeak(false).setSeverity(Severity.MAJOR).setCount(3),
- newGroup(RuleType.BUG).setInLeak(true).setSeverity(Severity.CRITICAL).setCount(5),
- newGroup(RuleType.BUG).setInLeak(true).setSeverity(Severity.MINOR).setCount(7),
- // not bugs
- newGroup(RuleType.CODE_SMELL).setInLeak(true).setCount(9),
- newGroup(RuleType.VULNERABILITY).setInLeak(true).setCount(11))
- .assertThatLeakValueIs(CoreMetrics.NEW_BUGS, 5 + 7);
- }
-
- @Test
- public void test_new_code_smells() {
- withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_CODE_SMELLS, 0.0);
-
- with(
- newGroup(RuleType.CODE_SMELL).setInLeak(false).setSeverity(Severity.MAJOR).setCount(3),
- newGroup(RuleType.CODE_SMELL).setInLeak(true).setSeverity(Severity.CRITICAL).setCount(5),
- newGroup(RuleType.CODE_SMELL).setInLeak(true).setSeverity(Severity.MINOR).setCount(7),
- // not code smells
- newGroup(RuleType.BUG).setInLeak(true).setCount(9),
- newGroup(RuleType.VULNERABILITY).setInLeak(true).setCount(11))
- .assertThatLeakValueIs(CoreMetrics.NEW_CODE_SMELLS, 5 + 7);
- }
-
- @Test
- public void test_new_vulnerabilities() {
- withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_VULNERABILITIES, 0.0);
-
- with(
- newGroup(RuleType.VULNERABILITY).setInLeak(false).setSeverity(Severity.MAJOR).setCount(3),
- newGroup(RuleType.VULNERABILITY).setInLeak(true).setSeverity(Severity.CRITICAL).setCount(5),
- newGroup(RuleType.VULNERABILITY).setInLeak(true).setSeverity(Severity.MINOR).setCount(7),
- // not vulnerabilities
- newGroup(RuleType.BUG).setInLeak(true).setCount(9),
- newGroup(RuleType.CODE_SMELL).setInLeak(true).setCount(11))
- .assertThatLeakValueIs(CoreMetrics.NEW_VULNERABILITIES, 5 + 7);
- }
-
- @Test
- public void test_new_security_hotspots() {
- withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS, 0.0);
-
- with(
- newGroup(RuleType.SECURITY_HOTSPOT).setInLeak(false).setSeverity(Severity.MAJOR).setCount(3),
- newGroup(RuleType.SECURITY_HOTSPOT).setInLeak(true).setSeverity(Severity.CRITICAL).setCount(5),
- newGroup(RuleType.SECURITY_HOTSPOT).setInLeak(true).setSeverity(Severity.MINOR).setCount(7),
- // not hotspots
- newGroup(RuleType.BUG).setInLeak(true).setCount(9),
- newGroup(RuleType.CODE_SMELL).setInLeak(true).setCount(11))
- .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS, 5 + 7);
- }
-
- @Test
- public void test_new_violations() {
- withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_VIOLATIONS, 0.0);
-
- with(
- newGroup(RuleType.BUG).setInLeak(true).setCount(5),
- newGroup(RuleType.CODE_SMELL).setInLeak(true).setCount(7),
- newGroup(RuleType.VULNERABILITY).setInLeak(true).setCount(9),
- // not in leak
- newGroup(RuleType.BUG).setInLeak(false).setCount(11),
- newGroup(RuleType.CODE_SMELL).setInLeak(false).setCount(13),
- newGroup(RuleType.VULNERABILITY).setInLeak(false).setCount(17))
- .assertThatLeakValueIs(CoreMetrics.NEW_VIOLATIONS, 5 + 7 + 9);
- }
-
- @Test
- public void test_new_blocker_violations() {
- withNoIssues()
- .assertThatLeakValueIs(CoreMetrics.NEW_BLOCKER_VIOLATIONS, 0.0);
-
- with(
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(true).setCount(3),
- newGroup(RuleType.BUG).setSeverity(Severity.BLOCKER).setInLeak(true).setCount(5),
- newGroup(RuleType.VULNERABILITY).setSeverity(Severity.BLOCKER).setInLeak(true).setCount(7),
- // not blocker
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(9),
- // not in leak
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(false).setCount(11),
- newGroup(RuleType.BUG).setSeverity(Severity.BLOCKER).setInLeak(false).setCount(13))
- .assertThatLeakValueIs(CoreMetrics.NEW_BLOCKER_VIOLATIONS, 3 + 5 + 7);
- }
-
- @Test
- public void test_new_critical_violations() {
- withNoIssues()
- .assertThatLeakValueIs(CoreMetrics.NEW_CRITICAL_VIOLATIONS, 0.0);
-
- with(
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(3),
- newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(5),
- newGroup(RuleType.VULNERABILITY).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(7),
- // not CRITICAL
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setInLeak(true).setCount(9),
- // not in leak
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(false).setCount(11),
- newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setInLeak(false).setCount(13))
- .assertThatLeakValueIs(CoreMetrics.NEW_CRITICAL_VIOLATIONS, 3 + 5 + 7);
- }
-
- @Test
- public void test_new_major_violations() {
- withNoIssues()
- .assertThatLeakValueIs(CoreMetrics.NEW_MAJOR_VIOLATIONS, 0.0);
-
- with(
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setInLeak(true).setCount(3),
- newGroup(RuleType.BUG).setSeverity(Severity.MAJOR).setInLeak(true).setCount(5),
- newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MAJOR).setInLeak(true).setCount(7),
- // not MAJOR
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(9),
- // not in leak
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setInLeak(false).setCount(11),
- newGroup(RuleType.BUG).setSeverity(Severity.MAJOR).setInLeak(false).setCount(13))
- .assertThatLeakValueIs(CoreMetrics.NEW_MAJOR_VIOLATIONS, 3 + 5 + 7);
- }
-
- @Test
- public void test_new_minor_violations() {
- withNoIssues()
- .assertThatLeakValueIs(CoreMetrics.NEW_MINOR_VIOLATIONS, 0.0);
-
- with(
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MINOR).setInLeak(true).setCount(3),
- newGroup(RuleType.BUG).setSeverity(Severity.MINOR).setInLeak(true).setCount(5),
- newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MINOR).setInLeak(true).setCount(7),
- // not MINOR
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(9),
- // not in leak
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MINOR).setInLeak(false).setCount(11),
- newGroup(RuleType.BUG).setSeverity(Severity.MINOR).setInLeak(false).setCount(13))
- .assertThatLeakValueIs(CoreMetrics.NEW_MINOR_VIOLATIONS, 3 + 5 + 7);
- }
-
- @Test
- public void test_new_info_violations() {
- withNoIssues()
- .assertThatLeakValueIs(CoreMetrics.NEW_INFO_VIOLATIONS, 0.0);
-
- with(
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.INFO).setInLeak(true).setCount(3),
- newGroup(RuleType.BUG).setSeverity(Severity.INFO).setInLeak(true).setCount(5),
- newGroup(RuleType.VULNERABILITY).setSeverity(Severity.INFO).setInLeak(true).setCount(7),
- // not INFO
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(9),
- // not in leak
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.INFO).setInLeak(false).setCount(11),
- newGroup(RuleType.BUG).setSeverity(Severity.INFO).setInLeak(false).setCount(13))
- .assertThatLeakValueIs(CoreMetrics.NEW_INFO_VIOLATIONS, 3 + 5 + 7);
- }
-
- @Test
- public void test_new_technical_debt() {
- withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0);
-
- with(
- newGroup(RuleType.CODE_SMELL).setEffort(3.0).setInLeak(true),
- // not in leak
- newGroup(RuleType.CODE_SMELL).setEffort(5.0).setInLeak(false),
- // not code smells
- newGroup(RuleType.SECURITY_HOTSPOT).setEffort(9.0).setInLeak(true),
- newGroup(RuleType.BUG).setEffort(7.0).setInLeak(true),
- // exclude resolved
- newResolvedGroup(RuleType.CODE_SMELL).setEffort(17.0).setInLeak(true))
- .assertThatLeakValueIs(CoreMetrics.NEW_TECHNICAL_DEBT, 3.0);
- }
-
- @Test
- public void test_new_reliability_remediation_effort() {
- withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT, 0.0);
-
- with(
- newGroup(RuleType.BUG).setEffort(3.0).setInLeak(true),
- // not in leak
- newGroup(RuleType.BUG).setEffort(5.0).setInLeak(false),
- // not bugs
- newGroup(RuleType.CODE_SMELL).setEffort(7.0).setInLeak(true),
- // exclude resolved
- newResolvedGroup(RuleType.BUG).setEffort(17.0).setInLeak(true))
- .assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT, 3.0);
- }
-
- @Test
- public void test_new_security_remediation_effort() {
- withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT, 0.0);
-
- with(
- newGroup(RuleType.VULNERABILITY).setEffort(3.0).setInLeak(true),
- // not in leak
- newGroup(RuleType.VULNERABILITY).setEffort(5.0).setInLeak(false),
- // not vulnerability
- newGroup(RuleType.CODE_SMELL).setEffort(7.0).setInLeak(true),
- // exclude resolved
- newResolvedGroup(RuleType.VULNERABILITY).setEffort(17.0).setInLeak(true))
- .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT, 3.0);
- }
-
- @Test
- public void test_new_reliability_rating() {
- withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_RATING, Rating.A);
-
- with(
- newGroup(RuleType.BUG).setSeverity(Severity.INFO).setCount(3).setInLeak(true),
- newGroup(RuleType.BUG).setSeverity(Severity.MINOR).setCount(1).setInLeak(true),
- // not in leak
- newGroup(RuleType.BUG).setSeverity(Severity.BLOCKER).setInLeak(false),
- // not bug
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(true),
- // exclude resolved
- newResolvedGroup(RuleType.BUG).setSeverity(Severity.BLOCKER).setInLeak(true))
- // highest severity of bugs on leak period is minor -> B
- .assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_RATING, Rating.B);
- }
-
- @Test
- public void test_new_security_rating() {
- withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_RATING, Rating.A);
-
- with(
- newGroup(RuleType.VULNERABILITY).setSeverity(Severity.INFO).setCount(3).setInLeak(true),
- newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MINOR).setCount(1).setInLeak(true),
- // not in leak
- newGroup(RuleType.VULNERABILITY).setSeverity(Severity.BLOCKER).setInLeak(false),
- // not vulnerability
- newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(true),
- // exclude resolved
- newResolvedGroup(RuleType.VULNERABILITY).setSeverity(Severity.BLOCKER).setInLeak(true))
- // highest severity of bugs on leak period is minor -> B
- .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_RATING, Rating.B);
- }
-
- @Test
- public void test_new_security_review_rating() {
- with(
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3).setInLeak(true),
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true),
- // not in leak
- newGroup(RuleType.SECURITY_HOTSPOT).setSeverity(Issue.STATUS_TO_REVIEW).setInLeak(false))
- .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_REVIEW_RATING, Rating.B);
-
- withNoIssues()
- .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_REVIEW_RATING, Rating.A);
- }
-
- @Test
- public void test_new_security_hotspots_reviewed() {
- with(
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3).setInLeak(true),
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true),
- // not in leak
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(5).setInLeak(false))
- .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED, 75.0);
-
- withNoIssues()
- .assertNoLeakValue(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED);
- }
-
- @Test
- public void test_new_security_hotspots_reviewed_status() {
- with(
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3).setInLeak(true),
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true),
- // not in leak
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(5).setInLeak(false))
- .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS, 3.0);
-
- withNoIssues()
- .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS, 0.0);
- }
-
- @Test
- public void test_new_security_hotspots_to_review_status() {
- with(
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3).setInLeak(true),
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true),
- // not in leak
- newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(5).setInLeak(false))
- .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1.0);
-
- withNoIssues()
- .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 0.0);
- }
-
- @Test
- public void test_new_sqale_debt_ratio_and_new_maintainability_rating() {
- withNoIssues()
- .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0)
- .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
-
- // technical_debt not computed
- withLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 0)
- .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0)
- .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
- withLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 20)
- .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0)
- .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
-
- // development_cost not computed
- withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 0)
- .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0)
- .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
- withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 20)
- .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0)
- .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
-
- // input measures are available
- withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 20.0)
- .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 0.0)
- .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0)
- .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
-
- withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 20.0)
- .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 160.0)
- .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 12.5)
- .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.C);
-
- withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 20.0)
- .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 10.0)
- .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 200.0)
- .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.E);
-
- // A is 5% --> min debt is exactly 200*0.05=10
- withLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 200.0)
- .andLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 10.0)
- .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 5.0)
- .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
-
- withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0)
- .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 0.0)
- .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0)
- .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
-
- withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0)
- .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 80.0)
- .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0);
-
- withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, -20.0)
- .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 0.0)
- .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0)
- .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
-
- // bug, debt can't be negative
- withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, -20.0)
- .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 80.0)
- .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0)
- .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
-
- // bug, cost can't be negative
- withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 20.0)
- .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, -80.0)
- .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0)
- .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
- }
-
- private Verifier with(IssueGroupDto... groups) {
- return new Verifier(groups);
- }
-
- private Verifier withNoIssues() {
- return new Verifier(new IssueGroupDto[0]);
- }
-
- private Verifier with(Metric metric, double value) {
- return new Verifier(new IssueGroupDto[0]).and(metric, value);
- }
-
- private Verifier withLeak(Metric metric, double leakValue) {
- return new Verifier(new IssueGroupDto[0]).andLeak(metric, leakValue);
- }
-
- private class Verifier {
- private final IssueGroupDto[] groups;
- private final Map<Metric, Double> values = new HashMap<>();
- private final Map<Metric, Double> leakValues = new HashMap<>();
-
- private Verifier(IssueGroupDto[] groups) {
- this.groups = groups;
- }
-
- Verifier and(Metric metric, double value) {
- this.values.put(metric, value);
- return this;
- }
-
- Verifier andLeak(Metric metric, double value) {
- this.leakValues.put(metric, value);
- return this;
- }
-
- Verifier assertThatValueIs(Metric metric, double expectedValue) {
- TestContext context = run(metric, false);
- assertThat(context.doubleValue).isNotNull().isEqualTo(expectedValue);
- return this;
- }
-
- Verifier assertThatLeakValueIs(Metric metric, double expectedValue) {
- TestContext context = run(metric, true);
- assertThat(context.doubleLeakValue).isNotNull().isEqualTo(expectedValue);
- return this;
- }
-
- Verifier assertThatLeakValueIs(Metric metric, Rating expectedRating) {
- TestContext context = run(metric, true);
- assertThat(context.ratingLeakValue).isNotNull().isEqualTo(expectedRating);
- return this;
- }
-
- Verifier assertNoLeakValue(Metric metric) {
- TestContext context = run(metric, true);
- assertThat(context.ratingLeakValue).isNull();
- return this;
- }
-
- Verifier assertThatValueIs(Metric metric, Rating expectedValue) {
- TestContext context = run(metric, false);
- assertThat(context.ratingValue).isNotNull().isEqualTo(expectedValue);
- return this;
- }
-
- Verifier assertNoValue(Metric metric) {
- TestContext context = run(metric, false);
- assertThat(context.ratingValue).isNull();
- return this;
- }
-
- private TestContext run(Metric metric, boolean expectLeakFormula) {
- IssueMetricFormula formula = underTest.getFormulas().stream()
- .filter(f -> f.getMetric().getKey().equals(metric.getKey()))
- .findFirst()
- .get();
- assertThat(formula.isOnLeak()).isEqualTo(expectLeakFormula);
- TestContext context = new TestContext(formula.getDependentMetrics(), values, leakValues);
- formula.compute(context, newIssueCounter(groups));
- return context;
- }
- }
-
- private static IssueCounter newIssueCounter(IssueGroupDto... issues) {
- return new IssueCounter(asList(issues));
- }
-
- private static IssueGroupDto newGroup() {
- return newGroup(RuleType.CODE_SMELL);
- }
-
- private static IssueGroupDto newGroup(RuleType ruleType) {
- IssueGroupDto dto = new IssueGroupDto();
- // set non-null fields
- dto.setRuleType(ruleType.getDbConstant());
- dto.setCount(1);
- dto.setEffort(0.0);
- dto.setSeverity(Severity.INFO);
- dto.setStatus(Issue.STATUS_OPEN);
- dto.setInLeak(false);
- return dto;
- }
-
- private static IssueGroupDto newResolvedGroup(RuleType ruleType) {
- return newGroup(ruleType).setResolution(Issue.RESOLUTION_FALSE_POSITIVE).setStatus(Issue.STATUS_CLOSED);
- }
-
- private static IssueGroupDto newResolvedGroup(String resolution, String status) {
- return newGroup().setResolution(resolution).setStatus(status);
- }
-
- private static class TestContext implements IssueMetricFormula.Context {
- private final Set<Metric> dependentMetrics;
- private Double doubleValue;
- private Rating ratingValue;
- private Double doubleLeakValue;
- private Rating ratingLeakValue;
- private final Map<Metric, Double> values;
- private final Map<Metric, Double> leakValues;
-
- private TestContext(Collection<Metric> dependentMetrics, Map<Metric, Double> values, Map<Metric, Double> leakValues) {
- this.dependentMetrics = new HashSet<>(dependentMetrics);
- this.values = values;
- this.leakValues = leakValues;
- }
-
- @Override
- public ComponentDto getComponent() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public DebtRatingGrid getDebtRatingGrid() {
- return new DebtRatingGrid(new double[] {0.05, 0.1, 0.2, 0.5});
- }
-
- @Override
- public Optional<Double> getValue(Metric metric) {
- if (!dependentMetrics.contains(metric)) {
- throw new IllegalStateException("Metric " + metric.getKey() + " is not declared as a dependency");
- }
- if (values.containsKey(metric)) {
- return Optional.of(values.get(metric));
- }
- return Optional.empty();
- }
-
- @Override
- public Optional<Double> getLeakValue(Metric metric) {
- if (!dependentMetrics.contains(metric)) {
- throw new IllegalStateException("Metric " + metric.getKey() + " is not declared as a dependency");
- }
- if (leakValues.containsKey(metric)) {
- return Optional.of(leakValues.get(metric));
- }
- return Optional.empty();
- }
-
- @Override
- public void setValue(double value) {
- this.doubleValue = value;
- }
-
- @Override
- public void setValue(Rating value) {
- this.ratingValue = value;
- }
-
- @Override
- public void setLeakValue(double value) {
- this.doubleLeakValue = value;
- }
-
- @Override
- public void setLeakValue(Rating value) {
- this.ratingLeakValue = value;
- }
- }
-}
*/
package org.sonar.server.measure.live;
-import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.util.Arrays;
-import java.util.Collection;
import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Supplier;
+import java.util.Set;
import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Rule;
import org.sonar.api.config.Configuration;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Metric;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.utils.System2;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.component.BranchDto;
-import org.sonar.db.component.BranchType;
import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.measure.LiveMeasureDto;
+import org.sonar.db.component.SnapshotDto;
import org.sonar.db.metric.MetricDto;
-import org.sonar.db.newcodeperiod.NewCodePeriodType;
-import org.sonar.db.project.ProjectDto;
-import org.sonar.server.es.ProjectIndexer;
import org.sonar.server.es.TestProjectIndexers;
-import org.sonar.server.measure.Rating;
import org.sonar.server.qualitygate.EvaluatedQualityGate;
import org.sonar.server.qualitygate.QualityGate;
import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
import org.sonar.server.setting.ProjectConfigurationLoader;
import org.sonar.server.setting.TestProjectConfigurationLoader;
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singleton;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static org.sonar.api.resources.Qualifiers.ORDERED_BOTTOM_UP;
+import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
@RunWith(DataProviderRunner.class)
public class LiveMeasureComputerImplTest {
public DbTester db = DbTester.create();
private final TestProjectIndexers projectIndexer = new TestProjectIndexers();
- private MetricDto intMetric;
- private MetricDto ratingMetric;
- private MetricDto alertStatusMetric;
+ private MetricDto metric1;
+ private MetricDto metric2;
private ComponentDto project;
- private ProjectDto projectDto;
- private ComponentDto dir;
- private ComponentDto file1;
- private ComponentDto file2;
- private ComponentDto prBranch;
- private ComponentDto prBranchFile;
- private ComponentDto branch;
- private ComponentDto branchFile;
+
private final LiveQualityGateComputer qGateComputer = mock(LiveQualityGateComputer.class);
private final QualityGate qualityGate = mock(QualityGate.class);
private final EvaluatedQualityGate newQualityGate = mock(EvaluatedQualityGate.class);
+ private final Configuration configuration = new MapSettings(new PropertyDefinitions(System2.INSTANCE, CorePropertyDefinitions.all())).asConfig();
+ private final ProjectConfigurationLoader configurationLoader = new TestProjectConfigurationLoader(configuration);
+ private final MeasureUpdateFormulaFactory measureUpdateFormulaFactory = mock(MeasureUpdateFormulaFactory.class);
+ private final ComponentIndexFactory componentIndexFactory = mock(ComponentIndexFactory.class);
+ private final ComponentIndex componentIndex = mock(ComponentIndex.class);
+ private final FakeLiveMeasureTreeUpdater treeUpdater = new FakeLiveMeasureTreeUpdater();
+ private final LiveMeasureComputerImpl liveMeasureComputer = new LiveMeasureComputerImpl(db.getDbClient(), measureUpdateFormulaFactory, componentIndexFactory,
+ qGateComputer, configurationLoader, projectIndexer, treeUpdater);
+ private BranchDto branch;
@Before
public void setUp() {
- intMetric = db.measures().insertMetric(m -> m.setValueType(Metric.ValueType.INT.name()));
- ratingMetric = db.measures().insertMetric(m -> m.setValueType(Metric.ValueType.RATING.name()));
- alertStatusMetric = db.measures().insertMetric(m -> m.setKey(CoreMetrics.ALERT_STATUS_KEY));
- project = db.components().insertPublicProject();
- projectDto = db.components().getProjectDto(project);
- dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java"));
- file1 = db.components().insertComponent(ComponentTesting.newFileDto(project, dir));
- file2 = db.components().insertComponent(ComponentTesting.newFileDto(project, dir));
-
- prBranch = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.PULL_REQUEST));
- prBranchFile = db.components().insertComponent(ComponentTesting.newFileDto(prBranch));
-
- branch = db.components().insertProjectBranch(project);
- branchFile = db.components().insertComponent(ComponentTesting.newFileDto(branch));
- }
-
- @Test
- public void compute_and_insert_measures_if_they_do_not_exist_yet() {
- markProjectAsAnalyzed(project);
-
- List<QGChangeEvent> result = run(asList(file1, file2), newQualifierBasedIntFormula(), newRatingConstantFormula(Rating.C));
-
- // 2 measures per component have been created
- // Numeric value depends on qualifier (see newQualifierBasedIntFormula())
- assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(8);
- assertThatIntMeasureHasValue(file1, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
- assertThatRatingMeasureHasValue(file1, Rating.C);
- assertThatIntMeasureHasValue(file2, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
- assertThatRatingMeasureHasValue(file2, Rating.C);
- assertThatIntMeasureHasValue(dir, ORDERED_BOTTOM_UP.indexOf(Qualifiers.DIRECTORY));
- assertThatRatingMeasureHasValue(dir, Rating.C);
- assertThatIntMeasureHasValue(project, ORDERED_BOTTOM_UP.indexOf(Qualifiers.PROJECT));
- assertThatRatingMeasureHasValue(project, Rating.C);
- assertThatProjectChanged(result, project);
- }
-
- @Test
- public void compute_and_update_measures_if_they_already_exist() {
- markProjectAsAnalyzed(project);
- db.measures().insertLiveMeasure(project, intMetric, m -> m.setValue(42.0));
- db.measures().insertLiveMeasure(dir, intMetric, m -> m.setValue(42.0));
- db.measures().insertLiveMeasure(file1, intMetric, m -> m.setValue(42.0));
- db.measures().insertLiveMeasure(file2, intMetric, m -> m.setValue(42.0));
-
- // generates values 1, 2, 3
- List<QGChangeEvent> result = run(file1, newQualifierBasedIntFormula());
-
- assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(4);
- assertThatProjectChanged(result, project);
-
- // Numeric value depends on qualifier (see newQualifierBasedIntFormula())
- assertThatIntMeasureHasValue(file1, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
- assertThatIntMeasureHasValue(dir, ORDERED_BOTTOM_UP.indexOf(Qualifiers.DIRECTORY));
- assertThatIntMeasureHasValue(project, ORDERED_BOTTOM_UP.indexOf(Qualifiers.PROJECT));
- // untouched
- assertThatIntMeasureHasValue(file2, 42.0);
- }
-
- @Test
- public void variation_is_refreshed_when_int_value_is_changed() {
- markProjectAsAnalyzed(project);
- // value is:
- // 42 on last analysis
- // 42-12=30 on beginning of leak period
- db.measures().insertLiveMeasure(project, intMetric, m -> m.setValue(42.0).setVariation(12.0));
-
- // new value is 44, so variation on leak period is 44-30=14
- List<QGChangeEvent> result = run(file1, newIntConstantFormula(44.0));
-
- LiveMeasureDto measure = assertThatIntMeasureHasValue(project, 44.0);
- assertThat(measure.getVariation()).isEqualTo(14.0);
- assertThatProjectChanged(result, project);
- }
-
- @Test
- public void variation_is_refreshed_when_rating_value_is_changed() {
- markProjectAsAnalyzed(project);
- // value is:
- // B on last analysis
- // D on beginning of leak period --> variation is -2
- db.measures().insertLiveMeasure(project, ratingMetric, m -> m.setValue((double) Rating.B.getIndex()).setData("B").setVariation(-2.0));
-
- // new value is C, so variation on leak period is D to C = -1
- List<QGChangeEvent> result = run(file1, newRatingConstantFormula(Rating.C));
-
- LiveMeasureDto measure = assertThatRatingMeasureHasValue(project, Rating.C);
- assertThat(measure.getVariation()).isEqualTo(-1.0);
- assertThatProjectChanged(result, project);
- }
-
- @Test
- public void variation_does_not_change_if_rating_value_does_not_change() {
- markProjectAsAnalyzed(project);
- // value is:
- // B on last analysis
- // D on beginning of leak period --> variation is -2
- db.measures().insertLiveMeasure(project, ratingMetric, m -> m.setValue((double) Rating.B.getIndex()).setData("B").setVariation(-2.0));
-
- // new value is still B, so variation on leak period is still -2
- List<QGChangeEvent> result = run(file1, newRatingConstantFormula(Rating.B));
-
- LiveMeasureDto measure = assertThatRatingMeasureHasValue(project, Rating.B);
- assertThat(measure.getVariation()).isEqualTo(-2.0);
- assertThatProjectChanged(result, project);
- }
-
- @Test
- public void refresh_leak_measures() {
- markProjectAsAnalyzed(project);
- db.measures().insertLiveMeasure(project, intMetric, m -> m.setVariation(42.0).setValue(null));
- db.measures().insertLiveMeasure(project, ratingMetric, m -> m.setVariation((double) Rating.E.getIndex()));
- db.measures().insertLiveMeasure(dir, intMetric, m -> m.setVariation(42.0).setValue(null));
- db.measures().insertLiveMeasure(dir, ratingMetric, m -> m.setVariation((double) Rating.D.getIndex()));
- db.measures().insertLiveMeasure(file1, intMetric, m -> m.setVariation(42.0).setValue(null));
- db.measures().insertLiveMeasure(file1, ratingMetric, m -> m.setVariation((double) Rating.C.getIndex()));
-
- // generates values 1, 2, 3 on leak measures
- List<QGChangeEvent> result = run(file1, newQualifierBasedIntLeakFormula(), newRatingLeakFormula(Rating.B));
-
- assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(6);
-
- // Numeric value depends on qualifier (see newQualifierBasedIntLeakFormula())
- assertThatIntMeasureHasLeakValue(file1, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
- assertThatRatingMeasureHasLeakValue(file1, Rating.B);
- assertThatIntMeasureHasLeakValue(dir, ORDERED_BOTTOM_UP.indexOf(Qualifiers.DIRECTORY));
- assertThatRatingMeasureHasLeakValue(dir, Rating.B);
- assertThatIntMeasureHasLeakValue(project, ORDERED_BOTTOM_UP.indexOf(Qualifiers.PROJECT));
- assertThatRatingMeasureHasLeakValue(project, Rating.B);
- assertThatProjectChanged(result, project);
- }
-
- @Test
- public void refresh_after_first_analysis() {
- markProjectAsAnalyzed(project, null);
- db.measures().insertLiveMeasure(project, intMetric, m -> m.setVariation(null).setValue(42.0));
- db.measures().insertLiveMeasure(project, ratingMetric, m -> m.setValue((double) Rating.E.getIndex()).setData(Rating.E.name()));
- db.measures().insertLiveMeasure(dir, intMetric, m -> m.setVariation(null).setValue(42.0));
- db.measures().insertLiveMeasure(dir, ratingMetric, m -> m.setValue((double) Rating.D.getIndex()).setData(Rating.D.name()));
- db.measures().insertLiveMeasure(file1, intMetric, m -> m.setVariation(null).setValue(42.0));
- db.measures().insertLiveMeasure(file1, ratingMetric, m -> m.setValue((double) Rating.C.getIndex()).setData(Rating.C.name()));
-
- List<QGChangeEvent> result = run(file1, newQualifierBasedIntLeakFormula(), newIntConstantFormula(1337));
-
- assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(6);
-
- assertThatIntMeasureHasValue(file1, 1337);
- assertThatRatingMeasureHasValue(file1, Rating.C);
- assertThatIntMeasureHasValue(dir, 1337);
- assertThatRatingMeasureHasValue(dir, Rating.D);
- assertThatIntMeasureHasValue(project, 1337);
- assertThatRatingMeasureHasValue(project, Rating.E);
- assertThatProjectChanged(result, project);
- }
-
- @Test
- public void calculate_new_metrics_if_it_is_pr_or_branch() {
- markProjectAsAnalyzed(prBranch, null);
- db.measures().insertLiveMeasure(prBranch, intMetric, m -> m.setVariation(42.0).setValue(null));
- db.measures().insertLiveMeasure(prBranchFile, intMetric, m -> m.setVariation(42.0).setValue(null));
-
- // generates values 1, 2, 3 on leak measures
- List<QGChangeEvent> result = run(prBranchFile, newQualifierBasedIntLeakFormula(), newRatingLeakFormula(Rating.B));
-
- assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(4);
-
- // Numeric value depends on qualifier (see newQualifierBasedIntLeakFormula())
- assertThatIntMeasureHasLeakValue(prBranchFile, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
- assertThatRatingMeasureHasLeakValue(prBranchFile, Rating.B);
- assertThatIntMeasureHasLeakValue(prBranch, ORDERED_BOTTOM_UP.indexOf(Qualifiers.PROJECT));
- assertThatRatingMeasureHasLeakValue(prBranch, Rating.B);
- assertThatProjectChanged(result, prBranch);
- }
-
- @Test
- public void calculate_new_metrics_if_it_is_branch_using_new_code_reference() {
- markProjectAsAnalyzed(branch, null, NewCodePeriodType.REFERENCE_BRANCH);
- db.measures().insertLiveMeasure(branch, intMetric, m -> m.setVariation(42.0).setValue(null));
- db.measures().insertLiveMeasure(branchFile, intMetric, m -> m.setVariation(42.0).setValue(null));
-
- // generates values 1, 2, 3 on leak measures
- List<QGChangeEvent> result = run(branchFile, newQualifierBasedIntLeakFormula(), newRatingLeakFormula(Rating.B));
-
- assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(4);
-
- // Numeric value depends on qualifier (see newQualifierBasedIntLeakFormula())
- assertThatIntMeasureHasLeakValue(branchFile, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
- assertThatRatingMeasureHasLeakValue(branchFile, Rating.B);
- assertThatIntMeasureHasLeakValue(branch, ORDERED_BOTTOM_UP.indexOf(Qualifiers.PROJECT));
- assertThatRatingMeasureHasLeakValue(branch, Rating.B);
- assertThatProjectChanged(result, branch);
- }
-
- @Test
- public void do_nothing_if_project_has_not_been_analyzed() {
- // project has no snapshots
- List<QGChangeEvent> result = run(file1, newIncrementalFormula());
- assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isZero();
- assertThatProjectNotChanged(result, project);
- }
-
- @Test
- public void do_nothing_if_input_components_are_empty() {
- List<QGChangeEvent> result = run(emptyList(), newIncrementalFormula());
-
- assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isZero();
- assertThatProjectNotChanged(result, project);
- }
-
- @Test
- public void refresh_multiple_projects_at_the_same_time() {
- markProjectAsAnalyzed(project);
- ComponentDto project2 = db.components().insertPublicProject();
- ComponentDto fileInProject2 = db.components().insertComponent(ComponentTesting.newFileDto(project2));
- markProjectAsAnalyzed(project2);
-
- List<QGChangeEvent> result = run(asList(file1, fileInProject2), newQualifierBasedIntFormula());
-
- // generated values depend on position of qualifier in Qualifiers.ORDERED_BOTTOM_UP (see formula)
- assertThatIntMeasureHasValue(file1, 0);
- assertThatIntMeasureHasValue(dir, 2);
- assertThatIntMeasureHasValue(project, 4);
- assertThatIntMeasureHasValue(fileInProject2, 0);
- assertThatIntMeasureHasValue(project2, 4);
-
- // no other measures generated
- assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(5);
- assertThatProjectChanged(result, project, project2);
- }
-
- @Test
- public void refresh_multiple_branches_at_the_same_time() {
- // FIXME
- }
-
- @Test
- public void event_contains_no_previousStatus_if_measure_does_not_exist() {
- markProjectAsAnalyzed(project);
-
- List<QGChangeEvent> result = run(file1);
-
- assertThat(result)
- .extracting(QGChangeEvent::getPreviousStatus)
- .containsExactly(Optional.empty());
- }
-
- @Test
- public void event_contains_no_previousStatus_if_measure_exists_and_has_no_value() {
- markProjectAsAnalyzed(project);
- db.measures().insertLiveMeasure(project, alertStatusMetric, m -> m.setData((String) null));
+ metric1 = db.measures().insertMetric();
+ metric2 = db.measures().insertMetric();
- List<QGChangeEvent> result = run(file1);
+ project = db.components().insertPublicProject();
+ branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), project.uuid()).get();
+ db.measures().insertLiveMeasure(project, metric2, lm -> lm.setValue(1d));
- assertThat(result)
- .extracting(QGChangeEvent::getPreviousStatus)
- .containsExactly(Optional.empty());
+ when(componentIndexFactory.create(any(), any())).thenReturn(componentIndex);
+ when(measureUpdateFormulaFactory.getFormulaMetrics()).thenReturn(Set.of(toMetric(metric1), toMetric(metric2)));
+ when(componentIndex.getBranch()).thenReturn(project);
}
@Test
- public void event_contains_no_previousStatus_if_measure_exists_and_is_empty() {
- markProjectAsAnalyzed(project);
- db.measures().insertLiveMeasure(project, alertStatusMetric, m -> m.setData(""));
+ public void loads_measure_matrix_and_calls_tree_updater() {
+ SnapshotDto snapshot = markProjectAsAnalyzed(project);
+ when(componentIndex.getAllUuids()).thenReturn(Set.of(project.uuid()));
- List<QGChangeEvent> result = run(file1);
+ liveMeasureComputer.refresh(db.getSession(), List.of(project));
- assertThat(result)
- .extracting(QGChangeEvent::getPreviousStatus)
- .containsExactly(Optional.empty());
- }
+ // tree updater was called
+ assertThat(treeUpdater.getMeasureMatrix()).isNotNull();
- @Test
- public void event_contains_no_previousStatus_if_measure_exists_and_is_not_a_level() {
- markProjectAsAnalyzed(project);
- db.measures().insertLiveMeasure(project, alertStatusMetric, m -> m.setData("fooBar"));
+ // measure matrix was loaded with formula's metrics and measures
+ assertThat(treeUpdater.getMeasureMatrix().getMetricByUuid(metric2.getUuid())).isNotNull();
+ assertThat(treeUpdater.getMeasureMatrix().getMeasure(project, metric2.getKey()).get().getValue()).isEqualTo(1d);
- List<QGChangeEvent> result = run(file1);
-
- assertThat(result)
- .extracting(QGChangeEvent::getPreviousStatus)
- .containsExactly(Optional.empty());
+ // new measures were persisted
+ assertThat(db.getDbClient().liveMeasureDao().selectMeasure(db.getSession(), project.uuid(), metric1.getKey()).get().getValue()).isEqualTo(2d);
}
@Test
- @UseDataProvider("metricLevels")
- public void event_contains_previousStatus_if_measure_exists(Metric.Level level) {
- markProjectAsAnalyzed(project);
- db.measures().insertLiveMeasure(project, alertStatusMetric, m -> m.setData(level.name()));
- db.measures().insertLiveMeasure(project, intMetric, m -> m.setVariation(42.0).setValue(null));
-
- List<QGChangeEvent> result = run(file1, newQualifierBasedIntLeakFormula());
+ public void refreshes_quality_gate() {
+ SnapshotDto snapshot = markProjectAsAnalyzed(project);
+ when(componentIndex.getAllUuids()).thenReturn(Set.of(project.uuid()));
+ when(qGateComputer.loadQualityGate(db.getSession(), db.components().getProjectDto(project), branch)).thenReturn(qualityGate);
- assertThat(result)
- .extracting(QGChangeEvent::getPreviousStatus)
- .containsExactly(Optional.of(level));
- }
+ liveMeasureComputer.refresh(db.getSession(), List.of(project));
- @DataProvider
- public static Object[][] metricLevels() {
- return Arrays.stream(Metric.Level.values())
- .map(l -> new Object[] {l})
- .toArray(Object[][]::new);
+ verify(qGateComputer).refreshGateStatus(eq(project), eq(qualityGate), any(MeasureMatrix.class), eq(configuration));
}
@Test
- public void event_contains_newQualityGate_computed_by_LiveQualityGateComputer() {
- markProjectAsAnalyzed(project);
- db.measures().insertLiveMeasure(project, alertStatusMetric, m -> m.setData(Metric.Level.ERROR.name()));
- db.measures().insertLiveMeasure(project, intMetric, m -> m.setVariation(42.0).setValue(null));
- BranchDto branch = db.getDbClient().branchDao().selectByBranchKey(db.getSession(), project.projectUuid(), "master")
- .orElseThrow(() -> new IllegalStateException("Can't find master branch"));
-
- List<QGChangeEvent> result = run(file1, newQualifierBasedIntLeakFormula());
-
- assertThat(result)
- .extracting(QGChangeEvent::getQualityGateSupplier)
- .extracting(Supplier::get)
- .containsExactly(Optional.of(newQualityGate));
- verify(qGateComputer).loadQualityGate(any(DbSession.class), argThat(p -> p.getUuid().equals(projectDto.getUuid())), eq(branch));
- verify(qGateComputer).getMetricsRelatedTo(qualityGate);
- verify(qGateComputer).refreshGateStatus(eq(project), same(qualityGate), any(MeasureMatrix.class), any(Configuration.class));
+ public void return_if_no_analysis_found() {
+ liveMeasureComputer.refresh(db.getSession(), List.of(project));
+ assertThat(treeUpdater.getMeasureMatrix()).isNull();
}
@Test
- public void exception_describes_context_when_a_formula_fails() {
- markProjectAsAnalyzed(project);
- Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
-
- assertThatThrownBy(() -> {
- run(project, new IssueMetricFormula(metric, false, (context, issueCounter) -> {
- throw new NullPointerException("BOOM");
- }));
- })
- .isInstanceOf(IllegalStateException.class)
- .hasMessage("Fail to compute " + metric.getKey() + " on " + project.getDbKey());
- }
-
- private List<QGChangeEvent> run(ComponentDto component, IssueMetricFormula... formulas) {
- return run(singleton(component), formulas);
- }
-
- private List<QGChangeEvent> run(Collection<ComponentDto> components, IssueMetricFormula... formulas) {
- IssueMetricFormulaFactory formulaFactory = new TestIssueMetricFormulaFactory(asList(formulas));
+ public void returns_qgate_event() {
+ SnapshotDto snapshot = markProjectAsAnalyzed(project);
+ when(componentIndex.getAllUuids()).thenReturn(Set.of(project.uuid()));
- when(qGateComputer.loadQualityGate(any(DbSession.class), any(ProjectDto.class), any(BranchDto.class)))
- .thenReturn(qualityGate);
- when(qGateComputer.getMetricsRelatedTo(qualityGate)).thenReturn(singleton(CoreMetrics.ALERT_STATUS_KEY));
- when(qGateComputer.refreshGateStatus(eq(project), same(qualityGate), any(MeasureMatrix.class), any(Configuration.class)))
- .thenReturn(newQualityGate);
- MapSettings settings = new MapSettings(new PropertyDefinitions(System2.INSTANCE, CorePropertyDefinitions.all()));
- ProjectConfigurationLoader configurationLoader = new TestProjectConfigurationLoader(settings.asConfig());
+ MetricDto alertStatusMetric = db.measures().insertMetric(m -> m.setKey(ALERT_STATUS_KEY));
+ db.measures().insertLiveMeasure(project, alertStatusMetric, lm -> lm.setData("OK"));
- LiveMeasureComputerImpl underTest = new LiveMeasureComputerImpl(db.getDbClient(), formulaFactory, qGateComputer, configurationLoader, projectIndexer);
+ when(qGateComputer.loadQualityGate(db.getSession(), db.components().getProjectDto(project), branch)).thenReturn(qualityGate);
+ when(qGateComputer.refreshGateStatus(eq(project), eq(qualityGate), any(MeasureMatrix.class), eq(configuration))).thenReturn(newQualityGate);
- return underTest.refresh(db.getSession(), components);
- }
+ List<QGChangeEvent> qgChangeEvents = liveMeasureComputer.refresh(db.getSession(), List.of(project));
- private void markProjectAsAnalyzed(ComponentDto p) {
- markProjectAsAnalyzed(p, 1_490_000_000L);
+ assertThat(qgChangeEvents).hasSize(1);
+ assertThat(qgChangeEvents.get(0).getBranch()).isEqualTo(branch);
+ assertThat(qgChangeEvents.get(0).getAnalysis()).isEqualTo(snapshot);
+ assertThat(qgChangeEvents.get(0).getProject()).isEqualTo(db.components().getProjectDto(project));
+ assertThat(qgChangeEvents.get(0).getPreviousStatus()).contains(Metric.Level.OK);
+ assertThat(qgChangeEvents.get(0).getProjectConfiguration()).isEqualTo(configuration);
+ assertThat(qgChangeEvents.get(0).getQualityGateSupplier().get()).contains(newQualityGate);
}
- private void markProjectAsAnalyzed(ComponentDto p, @Nullable Long periodDate) {
- assertThat(p.qualifier()).isEqualTo(Qualifiers.PROJECT);
- markProjectAsAnalyzed(p, periodDate, null);
+ private SnapshotDto markProjectAsAnalyzed(ComponentDto p) {
+ return markProjectAsAnalyzed(p, 1_490_000_000L);
}
- private void markProjectAsAnalyzed(ComponentDto p, @Nullable Long periodDate, @Nullable NewCodePeriodType type) {
+ private SnapshotDto markProjectAsAnalyzed(ComponentDto p, @Nullable Long periodDate) {
assertThat(p.qualifier()).isEqualTo(Qualifiers.PROJECT);
- db.components().insertSnapshot(p, s -> s.setPeriodDate(periodDate).setPeriodMode(type != null ? type.name() : null));
- }
-
- private LiveMeasureDto assertThatIntMeasureHasValue(ComponentDto component, double expectedValue) {
- LiveMeasureDto measure = db.getDbClient().liveMeasureDao().selectMeasure(db.getSession(), component.uuid(), intMetric.getKey()).get();
- assertThat(measure.getComponentUuid()).isEqualTo(component.uuid());
- assertThat(measure.getProjectUuid()).isEqualTo(component.projectUuid());
- assertThat(measure.getMetricUuid()).isEqualTo(intMetric.getUuid());
- assertThat(measure.getValue()).isEqualTo(expectedValue);
- return measure;
- }
-
- private LiveMeasureDto assertThatRatingMeasureHasValue(ComponentDto component, Rating expectedRating) {
- LiveMeasureDto measure = db.getDbClient().liveMeasureDao().selectMeasure(db.getSession(), component.uuid(), ratingMetric.getKey()).get();
- assertThat(measure.getComponentUuid()).isEqualTo(component.uuid());
- assertThat(measure.getProjectUuid()).isEqualTo(component.projectUuid());
- assertThat(measure.getMetricUuid()).isEqualTo(ratingMetric.getUuid());
- assertThat(measure.getValue()).isEqualTo(expectedRating.getIndex());
- assertThat(measure.getDataAsString()).isEqualTo(expectedRating.name());
- return measure;
- }
-
- private void assertThatIntMeasureHasLeakValue(ComponentDto component, double expectedValue) {
- LiveMeasureDto measure = db.getDbClient().liveMeasureDao().selectMeasure(db.getSession(), component.uuid(), intMetric.getKey()).get();
- assertThat(measure.getComponentUuid()).isEqualTo(component.uuid());
- assertThat(measure.getProjectUuid()).isEqualTo(component.projectUuid());
- assertThat(measure.getMetricUuid()).isEqualTo(intMetric.getUuid());
- assertThat(measure.getValue()).isNull();
- assertThat(measure.getVariation()).isEqualTo(expectedValue);
- }
-
- private void assertThatRatingMeasureHasLeakValue(ComponentDto component, Rating expectedValue) {
- LiveMeasureDto measure = db.getDbClient().liveMeasureDao().selectMeasure(db.getSession(), component.uuid(), ratingMetric.getKey()).get();
- assertThat(measure.getComponentUuid()).isEqualTo(component.uuid());
- assertThat(measure.getProjectUuid()).isEqualTo(component.projectUuid());
- assertThat(measure.getMetricUuid()).isEqualTo(ratingMetric.getUuid());
- assertThat(measure.getVariation()).isEqualTo(expectedValue.getIndex());
- }
-
- private IssueMetricFormula newIncrementalFormula() {
- Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
- AtomicInteger counter = new AtomicInteger();
- return new IssueMetricFormula(metric, false, (ctx, issues) -> ctx.setValue(counter.incrementAndGet()));
- }
-
- private IssueMetricFormula newIntConstantFormula(double constant) {
- Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
- return new IssueMetricFormula(metric, false, (ctx, issues) -> ctx.setValue(constant));
+ return db.components().insertSnapshot(p, s -> s.setPeriodDate(periodDate));
}
- private IssueMetricFormula newRatingConstantFormula(Rating constant) {
- Metric metric = new Metric.Builder(ratingMetric.getKey(), ratingMetric.getShortName(), Metric.ValueType.valueOf(ratingMetric.getValueType())).create();
- return new IssueMetricFormula(metric, false, (ctx, issues) -> ctx.setValue(constant));
+ private static Metric<?> toMetric(MetricDto metric) {
+ return new Metric.Builder(metric.getKey(), metric.getShortName(), Metric.ValueType.valueOf(metric.getValueType())).create();
}
- private IssueMetricFormula newRatingLeakFormula(Rating rating) {
- Metric metric = new Metric.Builder(ratingMetric.getKey(), ratingMetric.getShortName(), Metric.ValueType.valueOf(ratingMetric.getValueType())).create();
- return new IssueMetricFormula(metric, true, (ctx, issues) -> ctx.setLeakValue(rating));
- }
-
- private IssueMetricFormula newQualifierBasedIntFormula() {
- Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
- return new IssueMetricFormula(metric, false, (ctx, issues) -> ctx.setValue(ORDERED_BOTTOM_UP.indexOf(ctx.getComponent().qualifier())));
- }
+ private class FakeLiveMeasureTreeUpdater implements LiveMeasureTreeUpdater {
+ private MeasureMatrix measureMatrix;
- private IssueMetricFormula newQualifierBasedIntLeakFormula() {
- Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
- return new IssueMetricFormula(metric, true, (ctx, issues) -> ctx.setLeakValue(ORDERED_BOTTOM_UP.indexOf(ctx.getComponent().qualifier())));
- }
-
- private void assertThatProjectChanged(List<QGChangeEvent> events, ComponentDto... projects) {
- for (ComponentDto p : projects) {
- assertThat(projectIndexer.hasBeenCalled(p.uuid(), ProjectIndexer.Cause.MEASURE_CHANGE)).isTrue();
+ @Override
+ public void update(DbSession dbSession, SnapshotDto lastAnalysis, Configuration config, ComponentIndex components, BranchDto branch, MeasureMatrix measures) {
+ this.measureMatrix = measures;
+ measures.setValue(project, metric1.getKey(), 2d);
}
- assertThat(events).extracting(e -> e.getBranch().getUuid())
- .containsExactlyInAnyOrder(Arrays.stream(projects).map(ComponentDto::uuid).toArray(String[]::new));
+ public MeasureMatrix getMeasureMatrix() {
+ return measureMatrix;
+ }
}
- private void assertThatProjectNotChanged(List<QGChangeEvent> events, ComponentDto project) {
- assertThat(projectIndexer.hasBeenCalled(project.uuid(), ProjectIndexer.Cause.MEASURE_CHANGE)).isFalse();
- assertThat(events).isEmpty();
- }
}
public void verify_count_of_added_components() {
ListContainer container = new ListContainer();
new LiveMeasureModule().configure(container);
- assertThat(container.getAddedObjects()).hasSize(3);
+ assertThat(container.getAddedObjects()).isNotEmpty();
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.server.measure.live;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.measures.Metric;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.measure.LiveMeasureDto;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.db.newcodeperiod.NewCodePeriodType;
+import org.sonar.db.rule.RuleDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.sonar.api.CoreProperties.RATING_GRID;
+
+public class LiveMeasureTreeUpdaterImplTest {
+ @Rule
+ public DbTester db = DbTester.create();
+
+ private final Configuration config = new MapSettings().setProperty(RATING_GRID, "0.05,0.1,0.2,0.5").asConfig();
+ private final HotspotMeasureUpdater hotspotMeasureUpdater = mock(HotspotMeasureUpdater.class);
+ private LiveMeasureTreeUpdaterImpl treeUpdater;
+ private ComponentIndexImpl componentIndex;
+ private MeasureMatrix matrix;
+ private MetricDto metricDto;
+ private Metric metric;
+ private ComponentDto project;
+ private BranchDto branch;
+ private ComponentDto dir;
+ private ComponentDto file1;
+ private ComponentDto file2;
+ private SnapshotDto snapshot;
+
+ @Before
+ public void setUp() {
+ // insert project and file structure
+ project = db.components().insertPrivateProject();
+ branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), project.uuid()).get();
+ dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java"));
+ file1 = db.components().insertComponent(ComponentTesting.newFileDto(project, dir));
+ file2 = db.components().insertComponent(ComponentTesting.newFileDto(project, dir));
+
+ // other needed data
+ metricDto = db.measures().insertMetric(m -> m.setValueType(Metric.ValueType.INT.name()));
+ metric = new Metric.Builder(metricDto.getKey(), metricDto.getShortName(), Metric.ValueType.valueOf(metricDto.getValueType())).create();
+ matrix = new MeasureMatrix(List.of(project, dir, file1, file2), List.of(metricDto), List.of());
+ componentIndex = new ComponentIndexImpl(db.getDbClient());
+ }
+
+ @Test
+ public void should_aggregate_values_up_the_hierarchy() {
+ snapshot = db.components().insertSnapshot(project);
+ treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new AggregateValuesFormula(), hotspotMeasureUpdater);
+
+ componentIndex.load(db.getSession(), List.of(file1));
+ List<LiveMeasureDto> initialValues = List.of(
+ new LiveMeasureDto().setComponentUuid(file1.uuid()).setValue(1d).setMetricUuid(metricDto.getUuid()),
+ new LiveMeasureDto().setComponentUuid(file2.uuid()).setValue(1d).setMetricUuid(metricDto.getUuid()),
+ new LiveMeasureDto().setComponentUuid(dir.uuid()).setValue(1d).setMetricUuid(metricDto.getUuid()),
+ new LiveMeasureDto().setComponentUuid(project.uuid()).setValue(1d).setMetricUuid(metricDto.getUuid())
+ );
+ matrix = new MeasureMatrix(List.of(project, dir, file1, file2), List.of(metricDto), initialValues);
+ treeUpdater.update(db.getSession(), snapshot, config, componentIndex, branch, matrix);
+
+ assertThat(matrix.getChanged()).extracting(LiveMeasureDto::getComponentUuid).containsOnly(project.uuid(), dir.uuid());
+ assertThat(matrix.getMeasure(project, metric.getKey()).get().getValue()).isEqualTo(4d);
+ assertThat(matrix.getMeasure(dir, metric.getKey()).get().getValue()).isEqualTo(3d);
+ assertThat(matrix.getMeasure(file1, metric.getKey()).get().getValue()).isEqualTo(1d);
+ assertThat(matrix.getMeasure(file2, metric.getKey()).get().getValue()).isEqualTo(1d);
+ }
+
+ @Test
+ public void should_set_values_up_the_hierarchy() {
+ snapshot = db.components().insertSnapshot(project);
+ treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new SetValuesFormula(), hotspotMeasureUpdater);
+
+ componentIndex.load(db.getSession(), List.of(file1));
+ treeUpdater.update(db.getSession(), snapshot, config, componentIndex, branch, matrix);
+
+ assertThat(matrix.getChanged()).extracting(LiveMeasureDto::getComponentUuid).containsOnly(project.uuid(), dir.uuid(), file1.uuid());
+ assertThat(matrix.getMeasure(project, metric.getKey()).get().getValue()).isEqualTo(1d);
+ assertThat(matrix.getMeasure(dir, metric.getKey()).get().getValue()).isEqualTo(1d);
+ assertThat(matrix.getMeasure(file1, metric.getKey()).get().getValue()).isEqualTo(1d);
+ assertThat(matrix.getMeasure(file2, metric.getKey())).isEmpty();
+ }
+
+ @Test
+ public void dont_use_leak_formulas_if_no_period() {
+ snapshot = db.components().insertSnapshot(project, s -> s.setPeriodDate(null));
+ treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak(), hotspotMeasureUpdater);
+
+ componentIndex.load(db.getSession(), List.of(file1));
+ treeUpdater.update(db.getSession(), snapshot, config, componentIndex, branch, matrix);
+
+ assertThat(matrix.getChanged()).extracting(LiveMeasureDto::getComponentUuid).isEmpty();
+ }
+
+ @Test
+ public void use_leak_formulas_if_pr() {
+ snapshot = db.components().insertSnapshot(project, s -> s.setPeriodDate(null));
+ branch.setBranchType(BranchType.PULL_REQUEST);
+ treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak(), hotspotMeasureUpdater);
+
+ componentIndex.load(db.getSession(), List.of(file1));
+ treeUpdater.update(db.getSession(), snapshot, config, componentIndex, branch, matrix);
+
+ assertThat(matrix.getChanged()).extracting(LiveMeasureDto::getComponentUuid).containsOnly(project.uuid(), dir.uuid(), file1.uuid());
+ }
+
+ @Test
+ public void calculate_new_metrics_if_using_new_code_branch_reference() {
+ snapshot = db.components().insertSnapshot(project, s -> s.setPeriodMode(NewCodePeriodType.REFERENCE_BRANCH.name()));
+ treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak(), hotspotMeasureUpdater);
+
+ componentIndex.load(db.getSession(), List.of(file1));
+ treeUpdater.update(db.getSession(), snapshot, config, componentIndex, branch, matrix);
+
+ assertThat(matrix.getChanged()).extracting(LiveMeasureDto::getComponentUuid).containsOnly(project.uuid(), dir.uuid(), file1.uuid());
+ }
+
+ @Test
+ public void issue_counter_based_on_new_code_branch_reference() {
+ snapshot = db.components().insertSnapshot(project, s -> s.setPeriodMode(NewCodePeriodType.REFERENCE_BRANCH.name()));
+ treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak(), hotspotMeasureUpdater);
+
+ RuleDto rule = db.rules().insert();
+ IssueDto issue1 = db.issues().insertIssue(rule, project, file1);
+ IssueDto issue2 = db.issues().insertIssue(rule, project, file1);
+ db.issues().insertNewCodeReferenceIssue(issue1);
+
+ componentIndex.load(db.getSession(), List.of(file1));
+ treeUpdater.update(db.getSession(), snapshot, config, componentIndex, branch, matrix);
+ assertThat(matrix.getMeasure(file1, metric.getKey()).get().getVariation()).isEqualTo(1d);
+ }
+
+ @Test
+ public void issue_counter_uses_begin_of_leak() {
+ snapshot = db.components().insertSnapshot(project, s -> s.setPeriodDate(1000L));
+ treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak(), hotspotMeasureUpdater);
+
+ db.issues().insertIssue(i -> i.setIssueCreationDate(new Date(999)).setComponentUuid(file1.uuid()));
+ db.issues().insertIssue(i -> i.setIssueCreationDate(new Date(1001)).setComponentUuid(file1.uuid()));
+ db.issues().insertIssue(i -> i.setIssueCreationDate(new Date(1002)).setComponentUuid(file1.uuid()));
+
+ componentIndex.load(db.getSession(), List.of(file1));
+ treeUpdater.update(db.getSession(), snapshot, config, componentIndex, branch, matrix);
+
+ assertThat(matrix.getMeasure(file1, metric.getKey()).get().getVariation()).isEqualTo(2d);
+ }
+
+ @Test
+ public void calls_hotspot_updater() {
+ snapshot = db.components().insertSnapshot(project, s -> s.setPeriodDate(1000L));
+
+ componentIndex.load(db.getSession(), List.of(file1));
+ treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak(), hotspotMeasureUpdater);
+ treeUpdater.update(db.getSession(), snapshot, config, componentIndex, branch, matrix);
+ verify(hotspotMeasureUpdater).apply(eq(db.getSession()), any(), eq(componentIndex), eq(true), eq(1000L));
+ }
+
+ private class AggregateValuesFormula implements MeasureUpdateFormulaFactory {
+ @Override
+ public List<MeasureUpdateFormula> getFormulas() {
+ return List.of(new MeasureUpdateFormula(metric, false, new MeasureUpdateFormulaFactoryImpl.AddChildren(), (c, i) -> {
+ }));
+ }
+
+ @Override
+ public Set<Metric> getFormulaMetrics() {
+ return Set.of(metric);
+ }
+ }
+
+ private class SetValuesFormula implements MeasureUpdateFormulaFactory {
+ @Override
+ public List<MeasureUpdateFormula> getFormulas() {
+ return List.of(new MeasureUpdateFormula(metric, false, (c, m) -> {
+ }, (c, i) -> c.setValue(1d)));
+ }
+
+ @Override
+ public Set<Metric> getFormulaMetrics() {
+ return Set.of(metric);
+ }
+ }
+
+ private class CountUnresolvedInLeak implements MeasureUpdateFormulaFactory {
+ @Override
+ public List<MeasureUpdateFormula> getFormulas() {
+ return List.of(new MeasureUpdateFormula(metric, true, (c, m) -> {
+ }, (c, i) -> c.setLeakValue(i.countUnresolved(true))));
+ }
+
+ @Override
+ public Set<Metric> getFormulaMetrics() {
+ return Set.of(metric);
+ }
+ }
+}
private static final MetricDto METRIC_1 = newMetricDto().setUuid("100");
private static final MetricDto METRIC_2 = newMetricDto().setUuid("200");
-
@Test
public void getMetric() {
Collection<MetricDto> metrics = asList(METRIC_1, METRIC_2);
assertThat(underTest.getChanged()).hasSize(1);
verifyValue(underTest, PROJECT, metric, 3.56);
- verifyVariation(underTest, PROJECT, metric, 3.56 - (3.14 - 1.14));
+ verifyVariation(underTest, PROJECT, metric, 1.14);
}
@Test
MeasureMatrix underTest = new MeasureMatrix(asList(PROJECT), asList(metric), asList(measure));
underTest.setValue(PROJECT, metric.getKey(), 3.569);
+ underTest.setLeakValue(PROJECT, metric.getKey(), 3.569);
assertThat(underTest.getChanged()).hasSize(1);
verifyValue(underTest, PROJECT, metric, 3.57);
- verifyVariation(underTest, PROJECT, metric, 1.57);
+ verifyVariation(underTest, PROJECT, metric, 3.57);
}
@Test
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.server.measure.live;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.Nullable;
+import org.junit.Test;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.issue.IssueGroupDto;
+import org.sonar.server.measure.DebtRatingGrid;
+import org.sonar.server.measure.Rating;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_REVIEW_RATING;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_REVIEW_RATING;
+
+public class MeasureUpdateFormulaFactoryImplTest {
+
+ private final MeasureUpdateFormulaFactoryImpl underTest = new MeasureUpdateFormulaFactoryImpl();
+
+ @Test
+ public void getFormulaMetrics_include_the_dependent_metrics() {
+ for (MeasureUpdateFormula formula : underTest.getFormulas()) {
+ assertThat(underTest.getFormulaMetrics()).contains(formula.getMetric());
+ for (Metric<?> dependentMetric : formula.getDependentMetrics()) {
+ assertThat(underTest.getFormulaMetrics()).contains(dependentMetric);
+ }
+ }
+ }
+
+ @Test
+ public void hierarchy_adding_numbers() {
+ new HierarchyTester(CoreMetrics.VIOLATIONS)
+ .withValue(1d)
+ .withChildrenValues(2d, 3d)
+ .expectedResult(6d);
+
+ new HierarchyTester(CoreMetrics.BUGS)
+ .withValue(0d)
+ .withChildrenValues(2d, 3d)
+ .expectedResult(5d);
+
+ new HierarchyTester(CoreMetrics.NEW_BUGS)
+ .withValue(1d)
+ .expectedResult(1d);
+ }
+
+ @Test
+ public void hierarchy_highest_rating() {
+ new HierarchyTester(CoreMetrics.RELIABILITY_RATING)
+ .withValue(1d)
+ .withChildrenValues(2d, 3d)
+ .expectedRating(Rating.C);
+
+ // if no children, no need to set a value
+ new HierarchyTester(CoreMetrics.SECURITY_RATING)
+ .withValue(1d)
+ .expectedResult(null);
+
+ new HierarchyTester(CoreMetrics.NEW_RELIABILITY_RATING)
+ .withValue(5d)
+ .withChildrenValues(2d, 3d)
+ .expectedRating(Rating.E);
+ }
+
+ @Test
+ public void hierarchy_combining_other_metrics() {
+ new HierarchyTester(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED)
+ .withValue(SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1d)
+ .withValue(SECURITY_HOTSPOTS_REVIEWED_STATUS, 1d)
+ .expectedResult(50d);
+ new HierarchyTester(CoreMetrics.SECURITY_REVIEW_RATING)
+ .withValue(SECURITY_HOTSPOTS_REVIEWED, 100d)
+ .expectedRating(Rating.A);
+
+ new HierarchyTester(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED)
+ .withValue(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1d)
+ .withValue(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS, 1d)
+ .expectedResult(50d);
+ new HierarchyTester(CoreMetrics.NEW_SECURITY_REVIEW_RATING)
+ .withValue(NEW_SECURITY_HOTSPOTS_REVIEWED, 0d)
+ .expectedRating(Rating.E);
+ }
+
+ @Test
+ public void test_violations() {
+ withNoIssues().assertThatValueIs(CoreMetrics.VIOLATIONS, 0);
+ with(newGroup(), newGroup().setCount(4)).assertThatValueIs(CoreMetrics.VIOLATIONS, 5);
+
+ // exclude resolved
+ IssueGroupDto resolved = newResolvedGroup(Issue.RESOLUTION_FIXED, Issue.STATUS_RESOLVED);
+ with(newGroup(), newGroup(), resolved).assertThatValueIs(CoreMetrics.VIOLATIONS, 2);
+
+ // include issues on leak
+ IssueGroupDto onLeak = newGroup().setCount(11).setInLeak(true);
+ with(newGroup(), newGroup(), onLeak).assertThatValueIs(CoreMetrics.VIOLATIONS, 1 + 1 + 11);
+ }
+
+ @Test
+ public void test_bugs() {
+ withNoIssues().assertThatValueIs(CoreMetrics.BUGS, 0);
+ with(
+ newGroup(RuleType.BUG).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setCount(5),
+ // exclude resolved
+ newResolvedGroup(RuleType.BUG).setCount(7),
+ // not bugs
+ newGroup(RuleType.CODE_SMELL).setCount(11))
+ .assertThatValueIs(CoreMetrics.BUGS, 3 + 5);
+ }
+
+ @Test
+ public void test_code_smells() {
+ withNoIssues().assertThatValueIs(CoreMetrics.CODE_SMELLS, 0);
+ with(
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setCount(5),
+ // exclude resolved
+ newResolvedGroup(RuleType.CODE_SMELL).setCount(7),
+ // not code smells
+ newGroup(RuleType.BUG).setCount(11))
+ .assertThatValueIs(CoreMetrics.CODE_SMELLS, 3 + 5);
+ }
+
+ @Test
+ public void test_vulnerabilities() {
+ withNoIssues().assertThatValueIs(CoreMetrics.VULNERABILITIES, 0);
+ with(
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.CRITICAL).setCount(5),
+ // exclude resolved
+ newResolvedGroup(RuleType.VULNERABILITY).setCount(7),
+ // not vulnerabilities
+ newGroup(RuleType.BUG).setCount(11))
+ .assertThatValueIs(CoreMetrics.VULNERABILITIES, 3 + 5);
+ }
+
+ @Test
+ public void test_security_hotspots() {
+ withNoIssues().assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS, 0);
+ with(
+ newGroup(RuleType.SECURITY_HOTSPOT).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.SECURITY_HOTSPOT).setSeverity(Severity.CRITICAL).setCount(5),
+ // exclude resolved
+ newResolvedGroup(RuleType.SECURITY_HOTSPOT).setCount(7),
+ // not hotspots
+ newGroup(RuleType.BUG).setCount(11))
+ .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS, 3 + 5);
+ }
+
+ @Test
+ public void test_security_review_rating() {
+ with(
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3),
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1))
+ .assertThatValueIs(SECURITY_REVIEW_RATING, Rating.B);
+
+ withNoIssues()
+ .assertThatValueIs(SECURITY_REVIEW_RATING, Rating.A);
+ }
+
+ @Test
+ public void test_security_hotspots_reviewed() {
+ with(
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3),
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1))
+ .assertThatValueIs(SECURITY_HOTSPOTS_REVIEWED, 75.0);
+
+ withNoIssues()
+ .assertNoValue(SECURITY_HOTSPOTS_REVIEWED);
+ }
+
+ @Test
+ public void test_security_hotspots_reviewed_status() {
+ with(
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3),
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1))
+ .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS, 3.0);
+
+ withNoIssues()
+ .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS, 0.0);
+ }
+
+ @Test
+ public void test_security_hotspots_to_review_status() {
+ with(
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3),
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1))
+ .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1.0);
+
+ withNoIssues()
+ .assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 0.0);
+ }
+
+ @Test
+ public void count_unresolved_by_severity() {
+ withNoIssues()
+ .assertThatValueIs(CoreMetrics.BLOCKER_VIOLATIONS, 0)
+ .assertThatValueIs(CoreMetrics.CRITICAL_VIOLATIONS, 0)
+ .assertThatValueIs(CoreMetrics.MAJOR_VIOLATIONS, 0)
+ .assertThatValueIs(CoreMetrics.MINOR_VIOLATIONS, 0)
+ .assertThatValueIs(CoreMetrics.INFO_VIOLATIONS, 0);
+
+ with(
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.BUG).setSeverity(Severity.MAJOR).setCount(5),
+ newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setCount(7),
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setCount(11),
+ // exclude security hotspot
+ newGroup(RuleType.SECURITY_HOTSPOT).setSeverity(Severity.CRITICAL).setCount(15),
+ // include leak
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(true).setCount(13),
+ // exclude resolved
+ newResolvedGroup(RuleType.VULNERABILITY).setSeverity(Severity.INFO).setCount(17),
+ newResolvedGroup(RuleType.BUG).setSeverity(Severity.MAJOR).setCount(19),
+ newResolvedGroup(RuleType.SECURITY_HOTSPOT).setSeverity(Severity.INFO).setCount(21))
+ .assertThatValueIs(CoreMetrics.BLOCKER_VIOLATIONS, 11 + 13)
+ .assertThatValueIs(CoreMetrics.CRITICAL_VIOLATIONS, 7)
+ .assertThatValueIs(CoreMetrics.MAJOR_VIOLATIONS, 3 + 5)
+ .assertThatValueIs(CoreMetrics.MINOR_VIOLATIONS, 0)
+ .assertThatValueIs(CoreMetrics.INFO_VIOLATIONS, 0);
+ }
+
+ @Test
+ public void count_resolved() {
+ withNoIssues()
+ .assertThatValueIs(CoreMetrics.FALSE_POSITIVE_ISSUES, 0)
+ .assertThatValueIs(CoreMetrics.WONT_FIX_ISSUES, 0);
+
+ with(
+ newResolvedGroup(Issue.RESOLUTION_FIXED, Issue.STATUS_RESOLVED).setCount(3),
+ newResolvedGroup(Issue.RESOLUTION_FALSE_POSITIVE, Issue.STATUS_CLOSED).setCount(5),
+ newResolvedGroup(Issue.RESOLUTION_WONT_FIX, Issue.STATUS_CLOSED).setSeverity(Severity.MAJOR).setCount(7),
+ newResolvedGroup(Issue.RESOLUTION_WONT_FIX, Issue.STATUS_CLOSED).setSeverity(Severity.BLOCKER).setCount(11),
+ newResolvedGroup(Issue.RESOLUTION_REMOVED, Issue.STATUS_CLOSED).setCount(13),
+ // exclude security hotspot
+ newResolvedGroup(Issue.RESOLUTION_WONT_FIX, Issue.STATUS_RESOLVED).setCount(15).setRuleType(RuleType.SECURITY_HOTSPOT.getDbConstant()),
+ // exclude unresolved
+ newGroup(RuleType.VULNERABILITY).setCount(17),
+ newGroup(RuleType.BUG).setCount(19))
+ .assertThatValueIs(CoreMetrics.FALSE_POSITIVE_ISSUES, 5)
+ .assertThatValueIs(CoreMetrics.WONT_FIX_ISSUES, 7 + 11);
+ }
+
+ @Test
+ public void count_by_status() {
+ withNoIssues()
+ .assertThatValueIs(CoreMetrics.CONFIRMED_ISSUES, 0)
+ .assertThatValueIs(CoreMetrics.OPEN_ISSUES, 0)
+ .assertThatValueIs(CoreMetrics.REOPENED_ISSUES, 0);
+
+ with(
+ newGroup().setStatus(Issue.STATUS_CONFIRMED).setSeverity(Severity.BLOCKER).setCount(3),
+ newGroup().setStatus(Issue.STATUS_CONFIRMED).setSeverity(Severity.INFO).setCount(5),
+ newGroup().setStatus(Issue.STATUS_REOPENED).setCount(7),
+ newGroup(RuleType.CODE_SMELL).setStatus(Issue.STATUS_OPEN).setCount(9),
+ newGroup(RuleType.BUG).setStatus(Issue.STATUS_OPEN).setCount(11),
+ // exclude security hotspot
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN).setCount(12),
+ newResolvedGroup(Issue.RESOLUTION_FALSE_POSITIVE, Issue.STATUS_CLOSED).setCount(13))
+ .assertThatValueIs(CoreMetrics.CONFIRMED_ISSUES, 3 + 5)
+ .assertThatValueIs(CoreMetrics.OPEN_ISSUES, 9 + 11)
+ .assertThatValueIs(CoreMetrics.REOPENED_ISSUES, 7);
+ }
+
+ @Test
+ public void test_technical_debt() {
+ withNoIssues().assertThatValueIs(CoreMetrics.TECHNICAL_DEBT, 0);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setEffort(3.0).setInLeak(false),
+ newGroup(RuleType.CODE_SMELL).setEffort(5.0).setInLeak(true),
+ // exclude security hotspot
+ newGroup(RuleType.SECURITY_HOTSPOT).setEffort(9).setInLeak(true),
+ newGroup(RuleType.SECURITY_HOTSPOT).setEffort(11).setInLeak(false),
+ // not code smells
+ newGroup(RuleType.BUG).setEffort(7.0),
+ // exclude resolved
+ newResolvedGroup(RuleType.CODE_SMELL).setEffort(17.0))
+ .assertThatValueIs(CoreMetrics.TECHNICAL_DEBT, 3.0 + 5.0);
+ }
+
+ @Test
+ public void test_reliability_remediation_effort() {
+ withNoIssues().assertThatValueIs(CoreMetrics.RELIABILITY_REMEDIATION_EFFORT, 0);
+
+ with(
+ newGroup(RuleType.BUG).setEffort(3.0),
+ newGroup(RuleType.BUG).setEffort(5.0).setSeverity(Severity.BLOCKER),
+ // not bugs
+ newGroup(RuleType.CODE_SMELL).setEffort(7.0),
+ // exclude resolved
+ newResolvedGroup(RuleType.BUG).setEffort(17.0))
+ .assertThatValueIs(CoreMetrics.RELIABILITY_REMEDIATION_EFFORT, 3.0 + 5.0);
+ }
+
+ @Test
+ public void test_security_remediation_effort() {
+ withNoIssues().assertThatValueIs(CoreMetrics.SECURITY_REMEDIATION_EFFORT, 0);
+
+ with(
+ newGroup(RuleType.VULNERABILITY).setEffort(3.0),
+ newGroup(RuleType.VULNERABILITY).setEffort(5.0).setSeverity(Severity.BLOCKER),
+ // not vulnerability
+ newGroup(RuleType.CODE_SMELL).setEffort(7.0),
+ // exclude resolved
+ newResolvedGroup(RuleType.VULNERABILITY).setEffort(17.0))
+ .assertThatValueIs(CoreMetrics.SECURITY_REMEDIATION_EFFORT, 3.0 + 5.0);
+ }
+
+ @Test
+ public void test_sqale_debt_ratio_and_sqale_rating() {
+ withNoIssues()
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+
+ // technical_debt not computed
+ with(CoreMetrics.DEVELOPMENT_COST, "0")
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+ with(CoreMetrics.DEVELOPMENT_COST, "20")
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+
+ // development_cost not computed
+ with(CoreMetrics.TECHNICAL_DEBT, 0)
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+ with(CoreMetrics.TECHNICAL_DEBT, 20)
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+
+ // input measures are available
+ with(CoreMetrics.TECHNICAL_DEBT, 20.0)
+ .andText(CoreMetrics.DEVELOPMENT_COST, "0")
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+
+ with(CoreMetrics.TECHNICAL_DEBT, 20.0)
+ .andText(CoreMetrics.DEVELOPMENT_COST, "160")
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 12.5)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.C);
+
+ with(CoreMetrics.TECHNICAL_DEBT, 20.0)
+ .andText(CoreMetrics.DEVELOPMENT_COST, "10")
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 200.0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.E);
+
+ // A is 5% --> min debt is exactly 200*0.05=10
+ with(CoreMetrics.DEVELOPMENT_COST, "200")
+ .and(CoreMetrics.TECHNICAL_DEBT, 10.0)
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 5.0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+
+ with(CoreMetrics.TECHNICAL_DEBT, 0.0)
+ .andText(CoreMetrics.DEVELOPMENT_COST, "0")
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+
+ with(CoreMetrics.TECHNICAL_DEBT, 0.0)
+ .andText(CoreMetrics.DEVELOPMENT_COST, "80")
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0);
+
+ with(CoreMetrics.TECHNICAL_DEBT, -20.0)
+ .andText(CoreMetrics.DEVELOPMENT_COST, "0")
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+
+ // bug, debt can't be negative
+ with(CoreMetrics.TECHNICAL_DEBT, -20.0)
+ .andText(CoreMetrics.DEVELOPMENT_COST, "80")
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+
+ // bug, cost can't be negative
+ with(CoreMetrics.TECHNICAL_DEBT, 20.0)
+ .andText(CoreMetrics.DEVELOPMENT_COST, "-80")
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+ }
+
+ @Test
+ public void test_effort_to_reach_maintainability_rating_A() {
+ withNoIssues()
+ .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0);
+
+ // technical_debt not computed
+ with(CoreMetrics.DEVELOPMENT_COST, 0.0)
+ .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0);
+ with(CoreMetrics.DEVELOPMENT_COST, 20.0)
+ .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0);
+
+ // development_cost not computed
+ with(CoreMetrics.TECHNICAL_DEBT, 0.0)
+ .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0);
+ with(CoreMetrics.TECHNICAL_DEBT, 20.0)
+ // development cost is considered as zero, so the effort is to reach... zero
+ .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 20.0);
+
+ // B to A
+ with(CoreMetrics.DEVELOPMENT_COST, "200")
+ .and(CoreMetrics.TECHNICAL_DEBT, 40.0)
+ // B is 5% --> goal is to reach 200*0.05=10 --> effort is 40-10=30
+ .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 40.0 - (200.0 * 0.05));
+
+ // E to A
+ with(CoreMetrics.DEVELOPMENT_COST, "200")
+ .and(CoreMetrics.TECHNICAL_DEBT, 180.0)
+ // B is 5% --> goal is to reach 200*0.05=10 --> effort is 180-10=170
+ .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 180.0 - (200.0 * 0.05));
+
+ // already A
+ with(CoreMetrics.DEVELOPMENT_COST, "200")
+ .and(CoreMetrics.TECHNICAL_DEBT, 8.0)
+ // B is 5% --> goal is to reach 200*0.05=10 --> debt is already at 8 --> effort to reach A is zero
+ .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0);
+
+ // exactly lower range of B
+ with(CoreMetrics.DEVELOPMENT_COST, "200")
+ .and(CoreMetrics.TECHNICAL_DEBT, 10.0)
+ // B is 5% --> goal is to reach 200*0.05=10 --> debt is 10 --> effort to reach A is zero
+ // FIXME need zero to reach A but effective rating is B !
+ .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0);
+ }
+
+ @Test
+ public void test_reliability_rating() {
+ withNoIssues()
+ .assertThatValueIs(CoreMetrics.RELIABILITY_RATING, Rating.A);
+
+ with(
+ newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setCount(1),
+ newGroup(RuleType.BUG).setSeverity(Severity.MINOR).setCount(5),
+ // excluded, not a bug
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setCount(3))
+ // highest severity of bugs is CRITICAL --> D
+ .assertThatValueIs(CoreMetrics.RELIABILITY_RATING, Rating.D);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.CRITICAL).setCount(5))
+ // no bugs --> A
+ .assertThatValueIs(CoreMetrics.RELIABILITY_RATING, Rating.A);
+ }
+
+ @Test
+ public void test_security_rating() {
+ withNoIssues()
+ .assertThatValueIs(CoreMetrics.SECURITY_RATING, Rating.A);
+
+ with(
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.CRITICAL).setCount(1),
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MINOR).setCount(5),
+ // excluded, not a vulnerability
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setCount(3))
+ // highest severity of vulnerabilities is CRITICAL --> D
+ .assertThatValueIs(CoreMetrics.SECURITY_RATING, Rating.D);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setCount(5))
+ // no vulnerabilities --> A
+ .assertThatValueIs(CoreMetrics.SECURITY_RATING, Rating.A);
+ }
+
+ @Test
+ public void test_new_bugs() {
+ withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_BUGS, 0.0);
+
+ with(
+ newGroup(RuleType.BUG).setInLeak(false).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.BUG).setInLeak(true).setSeverity(Severity.CRITICAL).setCount(5),
+ newGroup(RuleType.BUG).setInLeak(true).setSeverity(Severity.MINOR).setCount(7),
+ // not bugs
+ newGroup(RuleType.CODE_SMELL).setInLeak(true).setCount(9),
+ newGroup(RuleType.VULNERABILITY).setInLeak(true).setCount(11))
+ .assertThatLeakValueIs(CoreMetrics.NEW_BUGS, 5 + 7);
+
+ }
+
+ @Test
+ public void test_new_code_smells() {
+ withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_CODE_SMELLS, 0.0);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setInLeak(false).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.CODE_SMELL).setInLeak(true).setSeverity(Severity.CRITICAL).setCount(5),
+ newGroup(RuleType.CODE_SMELL).setInLeak(true).setSeverity(Severity.MINOR).setCount(7),
+ // not code smells
+ newGroup(RuleType.BUG).setInLeak(true).setCount(9),
+ newGroup(RuleType.VULNERABILITY).setInLeak(true).setCount(11))
+ .assertThatLeakValueIs(CoreMetrics.NEW_CODE_SMELLS, 5 + 7);
+ }
+
+ @Test
+ public void test_new_vulnerabilities() {
+ withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_VULNERABILITIES, 0.0);
+
+ with(
+ newGroup(RuleType.VULNERABILITY).setInLeak(false).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.VULNERABILITY).setInLeak(true).setSeverity(Severity.CRITICAL).setCount(5),
+ newGroup(RuleType.VULNERABILITY).setInLeak(true).setSeverity(Severity.MINOR).setCount(7),
+ // not vulnerabilities
+ newGroup(RuleType.BUG).setInLeak(true).setCount(9),
+ newGroup(RuleType.CODE_SMELL).setInLeak(true).setCount(11))
+ .assertThatLeakValueIs(CoreMetrics.NEW_VULNERABILITIES, 5 + 7);
+ }
+
+ @Test
+ public void test_new_security_hotspots() {
+ withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS, 0.0);
+
+ with(
+ newGroup(RuleType.SECURITY_HOTSPOT).setInLeak(false).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.SECURITY_HOTSPOT).setInLeak(true).setSeverity(Severity.CRITICAL).setCount(5),
+ newGroup(RuleType.SECURITY_HOTSPOT).setInLeak(true).setSeverity(Severity.MINOR).setCount(7),
+ // not hotspots
+ newGroup(RuleType.BUG).setInLeak(true).setCount(9),
+ newGroup(RuleType.CODE_SMELL).setInLeak(true).setCount(11))
+ .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS, 5 + 7);
+ }
+
+ @Test
+ public void test_new_violations() {
+ withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_VIOLATIONS, 0.0);
+
+ with(
+ newGroup(RuleType.BUG).setInLeak(true).setCount(5),
+ newGroup(RuleType.CODE_SMELL).setInLeak(true).setCount(7),
+ newGroup(RuleType.VULNERABILITY).setInLeak(true).setCount(9),
+ // not in leak
+ newGroup(RuleType.BUG).setInLeak(false).setCount(11),
+ newGroup(RuleType.CODE_SMELL).setInLeak(false).setCount(13),
+ newGroup(RuleType.VULNERABILITY).setInLeak(false).setCount(17))
+ .assertThatLeakValueIs(CoreMetrics.NEW_VIOLATIONS, 5 + 7 + 9);
+ }
+
+ @Test
+ public void test_new_blocker_violations() {
+ withNoIssues()
+ .assertThatLeakValueIs(CoreMetrics.NEW_BLOCKER_VIOLATIONS, 0.0);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(true).setCount(3),
+ newGroup(RuleType.BUG).setSeverity(Severity.BLOCKER).setInLeak(true).setCount(5),
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.BLOCKER).setInLeak(true).setCount(7),
+ // not blocker
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(9),
+ // not in leak
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(false).setCount(11),
+ newGroup(RuleType.BUG).setSeverity(Severity.BLOCKER).setInLeak(false).setCount(13))
+ .assertThatLeakValueIs(CoreMetrics.NEW_BLOCKER_VIOLATIONS, 3 + 5 + 7);
+ }
+
+ @Test
+ public void test_new_critical_violations() {
+ withNoIssues()
+ .assertThatLeakValueIs(CoreMetrics.NEW_CRITICAL_VIOLATIONS, 0.0);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(3),
+ newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(5),
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(7),
+ // not CRITICAL
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setInLeak(true).setCount(9),
+ // not in leak
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(false).setCount(11),
+ newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setInLeak(false).setCount(13))
+ .assertThatLeakValueIs(CoreMetrics.NEW_CRITICAL_VIOLATIONS, 3 + 5 + 7);
+ }
+
+ @Test
+ public void test_new_major_violations() {
+ withNoIssues()
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAJOR_VIOLATIONS, 0.0);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setInLeak(true).setCount(3),
+ newGroup(RuleType.BUG).setSeverity(Severity.MAJOR).setInLeak(true).setCount(5),
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MAJOR).setInLeak(true).setCount(7),
+ // not MAJOR
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(9),
+ // not in leak
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setInLeak(false).setCount(11),
+ newGroup(RuleType.BUG).setSeverity(Severity.MAJOR).setInLeak(false).setCount(13))
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAJOR_VIOLATIONS, 3 + 5 + 7);
+ }
+
+ @Test
+ public void test_new_minor_violations() {
+ withNoIssues()
+ .assertThatLeakValueIs(CoreMetrics.NEW_MINOR_VIOLATIONS, 0.0);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MINOR).setInLeak(true).setCount(3),
+ newGroup(RuleType.BUG).setSeverity(Severity.MINOR).setInLeak(true).setCount(5),
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MINOR).setInLeak(true).setCount(7),
+ // not MINOR
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(9),
+ // not in leak
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MINOR).setInLeak(false).setCount(11),
+ newGroup(RuleType.BUG).setSeverity(Severity.MINOR).setInLeak(false).setCount(13))
+ .assertThatLeakValueIs(CoreMetrics.NEW_MINOR_VIOLATIONS, 3 + 5 + 7);
+ }
+
+ @Test
+ public void test_new_info_violations() {
+ withNoIssues()
+ .assertThatLeakValueIs(CoreMetrics.NEW_INFO_VIOLATIONS, 0.0);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.INFO).setInLeak(true).setCount(3),
+ newGroup(RuleType.BUG).setSeverity(Severity.INFO).setInLeak(true).setCount(5),
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.INFO).setInLeak(true).setCount(7),
+ // not INFO
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(9),
+ // not in leak
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.INFO).setInLeak(false).setCount(11),
+ newGroup(RuleType.BUG).setSeverity(Severity.INFO).setInLeak(false).setCount(13))
+ .assertThatLeakValueIs(CoreMetrics.NEW_INFO_VIOLATIONS, 3 + 5 + 7);
+ }
+
+ @Test
+ public void test_new_technical_debt() {
+ withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setEffort(3.0).setInLeak(true),
+ // not in leak
+ newGroup(RuleType.CODE_SMELL).setEffort(5.0).setInLeak(false),
+ // not code smells
+ newGroup(RuleType.SECURITY_HOTSPOT).setEffort(9.0).setInLeak(true),
+ newGroup(RuleType.BUG).setEffort(7.0).setInLeak(true),
+ // exclude resolved
+ newResolvedGroup(RuleType.CODE_SMELL).setEffort(17.0).setInLeak(true))
+ .assertThatLeakValueIs(CoreMetrics.NEW_TECHNICAL_DEBT, 3.0);
+ }
+
+ @Test
+ public void test_new_reliability_remediation_effort() {
+ withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT, 0.0);
+
+ with(
+ newGroup(RuleType.BUG).setEffort(3.0).setInLeak(true),
+ // not in leak
+ newGroup(RuleType.BUG).setEffort(5.0).setInLeak(false),
+ // not bugs
+ newGroup(RuleType.CODE_SMELL).setEffort(7.0).setInLeak(true),
+ // exclude resolved
+ newResolvedGroup(RuleType.BUG).setEffort(17.0).setInLeak(true))
+ .assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT, 3.0);
+ }
+
+ @Test
+ public void test_new_security_remediation_effort() {
+ withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT, 0.0);
+
+ with(
+ newGroup(RuleType.VULNERABILITY).setEffort(3.0).setInLeak(true),
+ // not in leak
+ newGroup(RuleType.VULNERABILITY).setEffort(5.0).setInLeak(false),
+ // not vulnerability
+ newGroup(RuleType.CODE_SMELL).setEffort(7.0).setInLeak(true),
+ // exclude resolved
+ newResolvedGroup(RuleType.VULNERABILITY).setEffort(17.0).setInLeak(true))
+ .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT, 3.0);
+ }
+
+ @Test
+ public void test_new_reliability_rating() {
+ withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_RATING, Rating.A);
+
+ with(
+ newGroup(RuleType.BUG).setSeverity(Severity.INFO).setCount(3).setInLeak(true),
+ newGroup(RuleType.BUG).setSeverity(Severity.MINOR).setCount(1).setInLeak(true),
+ // not in leak
+ newGroup(RuleType.BUG).setSeverity(Severity.BLOCKER).setInLeak(false),
+ // not bug
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(true),
+ // exclude resolved
+ newResolvedGroup(RuleType.BUG).setSeverity(Severity.BLOCKER).setInLeak(true))
+ // highest severity of bugs on leak period is minor -> B
+ .assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_RATING, Rating.B);
+ }
+
+ @Test
+ public void test_new_security_rating() {
+ withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_RATING, Rating.A);
+
+ with(
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.INFO).setCount(3).setInLeak(true),
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MINOR).setCount(1).setInLeak(true),
+ // not in leak
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.BLOCKER).setInLeak(false),
+ // not vulnerability
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(true),
+ // exclude resolved
+ newResolvedGroup(RuleType.VULNERABILITY).setSeverity(Severity.BLOCKER).setInLeak(true))
+ // highest severity of bugs on leak period is minor -> B
+ .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_RATING, Rating.B);
+ }
+
+ @Test
+ public void test_new_security_review_rating() {
+ with(
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3).setInLeak(true),
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true),
+ // not in leak
+ newGroup(RuleType.SECURITY_HOTSPOT).setSeverity(Issue.STATUS_TO_REVIEW).setInLeak(false))
+ .assertThatLeakValueIs(NEW_SECURITY_REVIEW_RATING, Rating.B);
+
+ withNoIssues()
+ .assertThatLeakValueIs(NEW_SECURITY_REVIEW_RATING, Rating.A);
+ }
+
+ @Test
+ public void test_new_security_hotspots_reviewed() {
+ with(
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3).setInLeak(true),
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true),
+ // not in leak
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(5).setInLeak(false))
+ .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED, 75.0);
+
+ withNoIssues()
+ .assertNoLeakValue(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED);
+ }
+
+ @Test
+ public void test_new_security_hotspots_reviewed_status() {
+ with(
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3).setInLeak(true),
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true),
+ // not in leak
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(5).setInLeak(false))
+ .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS, 3.0);
+
+ withNoIssues()
+ .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS, 0.0);
+ }
+
+ @Test
+ public void test_new_security_hotspots_to_review_status() {
+ with(
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setCount(3).setInLeak(true),
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(1).setInLeak(true),
+ // not in leak
+ newGroup(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW).setCount(5).setInLeak(false))
+ .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1.0);
+
+ withNoIssues()
+ .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 0.0);
+ }
+
+ @Test
+ public void test_new_sqale_debt_ratio_and_new_maintainability_rating() {
+ withNoIssues()
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
+
+ // technical_debt not computed
+ withLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
+ withLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 20)
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
+
+ // development_cost not computed
+ withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
+ withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 20)
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
+
+ // input measures are available
+ withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 20.0)
+ .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 0.0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
+
+ withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 20.0)
+ .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "160")
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 12.5)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.C);
+
+ withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 20.0)
+ .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "10")
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 200.0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.E);
+
+ // A is 5% --> min debt is exactly 200*0.05=10
+ with(CoreMetrics.NEW_DEVELOPMENT_COST, "200")
+ .andLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 10.0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 5.0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
+
+ withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0)
+ .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "0")
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
+
+ withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0)
+ .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "80")
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0);
+
+ withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, -20.0)
+ .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "0")
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
+
+ // bug, debt can't be negative
+ withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, -20.0)
+ .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "80")
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
+
+ // bug, cost can't be negative
+ withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 20.0)
+ .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "-80")
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
+ }
+
+ private Verifier with(IssueGroupDto... groups) {
+ return new Verifier(groups);
+ }
+
+ private Verifier withNoIssues() {
+ return new Verifier(new IssueGroupDto[0]);
+ }
+
+ private Verifier with(Metric metric, double value) {
+ return new Verifier(new IssueGroupDto[0]).and(metric, value);
+ }
+
+ private Verifier with(Metric metric, String value) {
+ return new Verifier(new IssueGroupDto[0]).andText(metric, value);
+ }
+
+ private Verifier withLeak(Metric metric, double leakValue) {
+ return new Verifier(new IssueGroupDto[0]).andLeak(metric, leakValue);
+ }
+
+ private class Verifier {
+ private final IssueGroupDto[] groups;
+ private final InitialValues initialValues = new InitialValues();
+
+ private Verifier(IssueGroupDto[] groups) {
+ this.groups = groups;
+ }
+
+ Verifier and(Metric metric, double value) {
+ this.initialValues.values.put(metric, value);
+ return this;
+ }
+
+ Verifier andLeak(Metric metric, double value) {
+ this.initialValues.leakValues.put(metric, value);
+ return this;
+ }
+
+ Verifier andText(Metric metric, String value) {
+ this.initialValues.text.put(metric, value);
+ return this;
+ }
+
+ Verifier assertThatValueIs(Metric metric, double expectedValue) {
+ TestContext context = run(metric, false);
+ assertThat(context.doubleValue).isNotNull().isEqualTo(expectedValue);
+ return this;
+ }
+
+ Verifier assertThatLeakValueIs(Metric metric, double expectedValue) {
+ TestContext context = run(metric, true);
+ assertThat(context.doubleLeakValue).isNotNull().isEqualTo(expectedValue);
+ return this;
+ }
+
+ Verifier assertThatLeakValueIs(Metric metric, Rating expectedRating) {
+ TestContext context = run(metric, true);
+ assertThat(context.ratingLeakValue).isNotNull().isEqualTo(expectedRating);
+ return this;
+ }
+
+ Verifier assertNoLeakValue(Metric metric) {
+ TestContext context = run(metric, true);
+ assertThat(context.ratingLeakValue).isNull();
+ return this;
+ }
+
+ Verifier assertThatValueIs(Metric metric, Rating expectedValue) {
+ TestContext context = run(metric, false);
+ assertThat(context.ratingValue).isNotNull().isEqualTo(expectedValue);
+ return this;
+ }
+
+ Verifier assertNoValue(Metric metric) {
+ TestContext context = run(metric, false);
+ assertThat(context.ratingValue).isNull();
+ return this;
+ }
+
+ private TestContext run(Metric metric, boolean expectLeakFormula) {
+ MeasureUpdateFormula formula = underTest.getFormulas().stream()
+ .filter(f -> f.getMetric().getKey().equals(metric.getKey()))
+ .findFirst()
+ .get();
+ assertThat(formula.isOnLeak()).isEqualTo(expectLeakFormula);
+ TestContext context = new TestContext(formula.getDependentMetrics(), initialValues);
+ formula.compute(context, newIssueCounter(groups));
+ return context;
+ }
+ }
+
+ private static IssueCounter newIssueCounter(IssueGroupDto... issues) {
+ return new IssueCounter(asList(issues));
+ }
+
+ private static IssueGroupDto newGroup() {
+ return newGroup(RuleType.CODE_SMELL);
+ }
+
+ private static IssueGroupDto newGroup(RuleType ruleType) {
+ IssueGroupDto dto = new IssueGroupDto();
+ // set non-null fields
+ dto.setRuleType(ruleType.getDbConstant());
+ dto.setCount(1);
+ dto.setEffort(0.0);
+ dto.setSeverity(Severity.INFO);
+ dto.setStatus(Issue.STATUS_OPEN);
+ dto.setInLeak(false);
+ return dto;
+ }
+
+ private static IssueGroupDto newResolvedGroup(RuleType ruleType) {
+ return newGroup(ruleType).setResolution(Issue.RESOLUTION_FALSE_POSITIVE).setStatus(Issue.STATUS_CLOSED);
+ }
+
+ private static IssueGroupDto newResolvedGroup(String resolution, String status) {
+ return newGroup().setResolution(resolution).setStatus(status);
+ }
+
+ private static class TestContext implements MeasureUpdateFormula.Context {
+ private final Set<Metric> dependentMetrics;
+ private final InitialValues initialValues;
+ private Double doubleValue;
+ private Rating ratingValue;
+ private Double doubleLeakValue;
+ private Rating ratingLeakValue;
+
+ private TestContext(Collection<Metric> dependentMetrics, InitialValues initialValues) {
+ this.dependentMetrics = new HashSet<>(dependentMetrics);
+ this.initialValues = initialValues;
+ }
+
+ @Override public List<Double> getChildrenValues() {
+ return initialValues.childrenValues;
+ }
+
+ @Override public List<Double> getChildrenLeakValues() {
+ return initialValues.childrenLeakValues;
+ }
+
+ @Override
+ public ComponentDto getComponent() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DebtRatingGrid getDebtRatingGrid() {
+ return new DebtRatingGrid(new double[] {0.05, 0.1, 0.2, 0.5});
+ }
+
+ @Override
+ public Optional<Double> getValue(Metric metric) {
+ if (!dependentMetrics.contains(metric)) {
+ throw new IllegalStateException("Metric " + metric.getKey() + " is not declared as a dependency");
+ }
+ if (initialValues.values.containsKey(metric)) {
+ return Optional.of(initialValues.values.get(metric));
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional<String> getText(Metric metric) {
+ if (initialValues.text.containsKey(metric)) {
+ return Optional.of(initialValues.text.get(metric));
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional<Double> getLeakValue(Metric metric) {
+ if (!dependentMetrics.contains(metric)) {
+ throw new IllegalStateException("Metric " + metric.getKey() + " is not declared as a dependency");
+ }
+ if (initialValues.leakValues.containsKey(metric)) {
+ return Optional.of(initialValues.leakValues.get(metric));
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public void setValue(double value) {
+ this.doubleValue = value;
+ }
+
+ @Override
+ public void setValue(Rating value) {
+ this.ratingValue = value;
+ }
+
+ @Override
+ public void setLeakValue(double value) {
+ this.doubleLeakValue = value;
+ }
+
+ @Override
+ public void setLeakValue(Rating value) {
+ this.ratingLeakValue = value;
+ }
+ }
+
+ private class InitialValues {
+ private final Map<Metric, Double> values = new HashMap<>();
+ private final Map<Metric, Double> leakValues = new HashMap<>();
+ private final List<Double> childrenValues = new ArrayList<>();
+ private final List<Double> childrenLeakValues = new ArrayList<>();
+ private final Map<Metric, String> text = new HashMap<>();
+ }
+
+ private class HierarchyTester {
+ private final Metric metric;
+ private final InitialValues initialValues;
+ private final MeasureUpdateFormula formula;
+
+ public HierarchyTester(Metric metric) {
+ this.metric = metric;
+ this.initialValues = new InitialValues();
+ this.formula = underTest.getFormulas().stream().filter(f -> f.getMetric().equals(metric)).findAny().get();
+ }
+
+ public HierarchyTester withValue(Metric metric, Double value) {
+ if (formula.isOnLeak()) {
+ this.initialValues.leakValues.put(metric, value);
+ } else {
+ this.initialValues.values.put(metric, value);
+ }
+ return this;
+ }
+
+ public HierarchyTester withValue(Double value) {
+ return withValue(metric, value);
+ }
+
+ public HierarchyTester withChildrenValues(Double... values) {
+ if (formula.isOnLeak()) {
+ this.initialValues.childrenLeakValues.addAll(asList(values));
+ } else {
+ this.initialValues.childrenValues.addAll(asList(values));
+ }
+ return this;
+ }
+
+ public HierarchyTester expectedResult(@Nullable Double expected) {
+ TestContext ctx = run();
+ if (formula.isOnLeak()) {
+ assertThat(ctx.doubleLeakValue).isEqualTo(expected);
+ } else {
+ assertThat(ctx.doubleValue).isEqualTo(expected);
+ }
+ return this;
+ }
+
+ public HierarchyTester expectedRating(@Nullable Rating rating) {
+ TestContext ctx = run();
+ if (formula.isOnLeak()) {
+ assertThat(ctx.ratingLeakValue).isEqualTo(rating);
+ } else {
+ assertThat(ctx.ratingValue).isEqualTo(rating);
+ }
+ return this;
+ }
+
+ private TestContext run() {
+ List<Metric> deps = new LinkedList<>(formula.getDependentMetrics());
+ deps.add(formula.getMetric());
+ deps.addAll(initialValues.values.keySet());
+ deps.addAll(initialValues.leakValues.keySet());
+ deps.addAll(initialValues.text.keySet());
+ TestContext context = new TestContext(deps, initialValues);
+ formula.computeHierarchy(context);
+ return context;
+ }
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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.server.measure.live;
-
-import java.util.List;
-import java.util.Set;
-import org.sonar.api.measures.Metric;
-
-class TestIssueMetricFormulaFactory implements IssueMetricFormulaFactory {
-
- private final List<IssueMetricFormula> formulas;
-
- TestIssueMetricFormulaFactory(List<IssueMetricFormula> formulas) {
- this.formulas = formulas;
- }
-
- @Override
- public List<IssueMetricFormula> getFormulas() {
- return formulas;
- }
-
- @Override
- public Set<Metric> getFormulaMetrics() {
- return IssueMetricFormulaFactory.extractMetrics(formulas);
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.server.measure.live;
+
+import java.util.List;
+import java.util.Set;
+import org.sonar.api.measures.Metric;
+
+class TestMeasureUpdateFormulaFactory implements MeasureUpdateFormulaFactory {
+
+ private final List<MeasureUpdateFormula> formulas;
+
+ TestMeasureUpdateFormulaFactory(List<MeasureUpdateFormula> formulas) {
+ this.formulas = formulas;
+ }
+
+ @Override
+ public List<MeasureUpdateFormula> getFormulas() {
+ return formulas;
+ }
+
+ @Override
+ public Set<Metric> getFormulaMetrics() {
+ return MeasureUpdateFormulaFactory.extractMetrics(formulas);
+ }
+}