* If Main Branch = 100 LOCs and the "largest long-lived branch" is 80 LOCs, I'm expecting to consider the value 100.
*/
public long sumNclocOfBiggestLongLivingBranch(DbSession dbSession) {
- Long ncloc = mapper(dbSession).sumNclocOfBiggestLongLivingBranch(NCLOC_KEY, KeyType.BRANCH, BranchType.LONG);
+ return sumNclocOfBiggestLongLivingBranch(dbSession, SumNclocDbQuery.builder().build());
+ }
+
+ public long sumNclocOfBiggestLongLivingBranch(DbSession dbSession, SumNclocDbQuery dbQuery) {
+ Long ncloc = mapper(dbSession).sumNclocOfBiggestLongLivingBranch(NCLOC_KEY, KeyType.BRANCH, BranchType.LONG, dbQuery.getProjectUuidToExclude());
return ncloc == null ? 0L : ncloc;
}
Long sumNclocOfBiggestLongLivingBranch(
@Param("ncloc") String nclocKey,
@Param("branch") KeyType branchOrPullRequest,
- @Param("branchType") BranchType branchType);
+ @Param("branchType") BranchType branchType,
+ @Nullable @Param("projectUuidToExclude") String projectUuidToExclude);
void insert(
@Param("dto") LiveMeasureDto dto,
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.measure;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+public class SumNclocDbQuery {
+
+ private final String projectUuidToExclude;
+
+ public SumNclocDbQuery(Builder builder) {
+ projectUuidToExclude = builder.projectUuidToExclude;
+ }
+
+ @CheckForNull
+ public String getProjectUuidToExclude() {
+ return projectUuidToExclude;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String projectUuidToExclude;
+
+ private Builder() {
+ // to enforce use of builder()
+ }
+
+ public Builder setProjectUuidToExclude(@Nullable String projectUuidToExclude) {
+ this.projectUuidToExclude = projectUuidToExclude;
+ return this;
+ }
+
+ public SumNclocDbQuery build() {
+ return new SumNclocDbQuery(this);
+ }
+ }
+}
inner join metrics m on m.id = lm.metric_id
inner join projects p on p.uuid = lm.component_uuid
inner join project_branches b on b.uuid = p.uuid
- where
+ <where>
m.name = #{ncloc, jdbcType=VARCHAR}
and p.enabled = ${_true}
and p.scope = 'PRJ'
and p.copy_component_uuid is null
and b.branch_type = #{branchType, jdbcType=VARCHAR}
and b.key_type = #{branch, jdbcType=VARCHAR}
+ <if test="projectUuidToExclude != null">
+ and b.project_uuid <> #{projectUuidToExclude,jdbcType=VARCHAR}
+ </if>
+ </where>
group by b.project_uuid
) sumncloc
</select>
db.measures().insertLiveMeasure(simpleProject, ncloc, m -> m.setValue(10d));
ComponentDto projectWithBiggerLongLivingBranch = db.components().insertMainBranch(organization);
- ComponentDto bigLongLivingBranch = db.components().insertProjectBranch(projectWithBiggerLongLivingBranch, b -> b.setBranchType(BranchType.LONG));
+ ComponentDto bigLongLivingLongBranch = db.components().insertProjectBranch(projectWithBiggerLongLivingBranch, b -> b.setBranchType(BranchType.LONG));
db.measures().insertLiveMeasure(projectWithBiggerLongLivingBranch, ncloc, m -> m.setValue(100d));
- db.measures().insertLiveMeasure(bigLongLivingBranch, ncloc, m -> m.setValue(200d));
+ db.measures().insertLiveMeasure(bigLongLivingLongBranch, ncloc, m -> m.setValue(200d));
ComponentDto projectWithLinesButNoLoc = db.components().insertMainBranch(organization);
db.measures().insertLiveMeasure(projectWithLinesButNoLoc, lines, m -> m.setValue(365d));
assertThat(result).isEqualTo(0L);
}
+ @Test
+ public void countNcloc_and_exclude_project() {
+ OrganizationDto organization = db.organizations().insert();
+ MetricDto ncloc = db.measures().insertMetric(m -> m.setKey("ncloc").setValueType(INT.toString()));
+
+ ComponentDto simpleProject = db.components().insertMainBranch(organization);
+ db.measures().insertLiveMeasure(simpleProject, ncloc, m -> m.setValue(10d));
+
+ ComponentDto projectWithBiggerLongLivingBranch = db.components().insertMainBranch(organization);
+ ComponentDto bigLongLivingBranch = db.components().insertProjectBranch(projectWithBiggerLongLivingBranch, b -> b.setBranchType(BranchType.LONG));
+ db.measures().insertLiveMeasure(projectWithBiggerLongLivingBranch, ncloc, m -> m.setValue(100d));
+ db.measures().insertLiveMeasure(bigLongLivingBranch, ncloc, m -> m.setValue(200d));
+
+ ComponentDto projectToExclude = db.components().insertMainBranch(organization);
+ ComponentDto projectToExcludeBranch = db.components().insertProjectBranch(projectToExclude, b -> b.setBranchType(BranchType.LONG));
+ db.measures().insertLiveMeasure(projectToExclude, ncloc, m -> m.setValue(300d));
+ db.measures().insertLiveMeasure(projectToExcludeBranch, ncloc, m -> m.setValue(400d));
+
+ long result = underTest.sumNclocOfBiggestLongLivingBranch(db.getSession(), SumNclocDbQuery.builder().setProjectUuidToExclude(projectToExclude.uuid()).build());
+
+ assertThat(result).isEqualTo(10L + 200L);
+ }
+
@Test
public void insert_data() {
byte[] data = "text_value".getBytes(StandardCharsets.UTF_8);
import org.sonar.server.computation.task.projectanalysis.filemove.FileMoveDetectionStep;
import org.sonar.server.computation.task.step.ComputationStep;
import org.sonar.server.computation.task.step.ExecuteStatelessInitExtensionsStep;
+import org.sonar.server.computation.task.step.PostMeasuresComputationChecksStep;
/**
* Ordered list of steps classes and instances to be executed for batch processing
LoadMeasureComputersStep.class,
ExecuteVisitorsStep.class,
+ PostMeasuresComputationChecksStep.class,
+
// Must be executed after computation of all measures
ComputeMeasureVariationsStep.class,
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.computation.task.step;
+
+import org.sonar.api.ExtensionPoint;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
+
+/**
+ * Extension point that is called during processing of a task
+ * by {@link PostMeasuresComputationChecksStep}.
+ *
+ * It is stateless, the same instance is reused for all tasks.
+ * As a consequence Compute Engine task components can't be injected
+ * as dependencies.
+ */
+@ComputeEngineSide
+@ExtensionPoint
+public interface PostMeasuresComputationCheck {
+
+ /**
+ * This method can make the task fail by throwing a {@link RuntimeException}
+ */
+ void onCheck(Context context);
+
+ interface Context {
+
+ /**
+ * Return the project UUID, as returned by {@link AnalysisMetadataHolder#getProject()#getUuid()}.
+ *
+ * It means that when analyzing a non main-branch, it will be the UUID of the project, not the UUId of the branch/pull-request.
+ */
+ String getProjectUuid();
+
+ /**
+ * Return the ncloc computed for the current analysis
+ */
+ int getNcloc();
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.computation.task.step;
+
+import com.google.common.base.Optional;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
+import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder;
+import org.sonar.server.computation.task.projectanalysis.measure.Measure;
+import org.sonar.server.computation.task.projectanalysis.measure.MeasureRepository;
+import org.sonar.server.computation.task.projectanalysis.metric.Metric;
+import org.sonar.server.computation.task.projectanalysis.metric.MetricRepository;
+
+/**
+ * Execute {@link PostMeasuresComputationCheck} instances in no specific order.
+ * If an extension fails (throws an exception), consecutive extensions
+ * won't be called.
+ */
+@ComputeEngineSide
+public class PostMeasuresComputationChecksStep implements ComputationStep {
+
+ private final TreeRootHolder treeRootHolder;
+ private final MetricRepository metricRepository;
+ private final MeasureRepository measureRepository;
+ private final AnalysisMetadataHolder analysisMetadataHolder;
+ private final PostMeasuresComputationCheck[] extensions;
+
+ public PostMeasuresComputationChecksStep(TreeRootHolder treeRootHolder, MetricRepository metricRepository, MeasureRepository measureRepository,
+ AnalysisMetadataHolder analysisMetadataHolder,
+ PostMeasuresComputationCheck[] extensions) {
+ this.treeRootHolder = treeRootHolder;
+ this.metricRepository = metricRepository;
+ this.measureRepository = measureRepository;
+ this.analysisMetadataHolder = analysisMetadataHolder;
+ this.extensions = extensions;
+ }
+
+ /**
+ * Used when zero {@link PostMeasuresComputationCheck} are registered into container.
+ */
+ public PostMeasuresComputationChecksStep(TreeRootHolder treeRootHolder, MetricRepository metricRepository, MeasureRepository measureRepository,
+ AnalysisMetadataHolder analysisMetadataHolder) {
+ this(treeRootHolder, metricRepository, measureRepository, analysisMetadataHolder, new PostMeasuresComputationCheck[0]);
+ }
+
+ @Override
+ public void execute() {
+ PostMeasuresComputationCheck.Context context = new ContextImpl();
+ for (PostMeasuresComputationCheck extension : extensions) {
+ extension.onCheck(context);
+ }
+ }
+
+ @Override
+ public String getDescription() {
+ return "Checks executed after computation of measures";
+ }
+
+ private class ContextImpl implements PostMeasuresComputationCheck.Context {
+
+ @Override
+ public String getProjectUuid() {
+ return analysisMetadataHolder.getProject().getUuid();
+ }
+
+ @Override
+ public int getNcloc() {
+ Metric nclocMetric = metricRepository.getByKey(CoreMetrics.NCLOC_KEY);
+ Optional<Measure> nclocMeasure = measureRepository.getRawMeasure(treeRootHolder.getRoot(), nclocMetric);
+ return nclocMeasure.isPresent() ? nclocMeasure.get().getIntValue() : 0;
+ }
+ }
+}
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.core.platform.ComponentContainer;
-import org.sonar.core.platform.ContainerPopulator;
-import org.sonar.server.computation.task.container.TaskContainer;
import org.sonar.server.computation.task.container.TaskContainerImpl;
import static org.mockito.Mockito.mock;
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Component not found: " + ExtractReportStep.class);
- TaskContainerImpl computeEngineContainer = new TaskContainerImpl(new ComponentContainer(), new ContainerPopulator<TaskContainer>() {
- @Override
- public void populateContainer(TaskContainer container) {
- // do nothing
- }
+ TaskContainerImpl computeEngineContainer = new TaskContainerImpl(new ComponentContainer(), container -> {
+ // do nothing
});
Lists.newArrayList(new ReportComputationSteps(computeEngineContainer).instances());
addSingleton(reportExtractionStep);
}
};
- TaskContainerImpl computeEngineContainer = new TaskContainerImpl(componentContainer, new ContainerPopulator<TaskContainer>() {
- @Override
- public void populateContainer(TaskContainer container) {
- // do nothing
- }
+ TaskContainerImpl computeEngineContainer = new TaskContainerImpl(componentContainer, container -> {
+ // do nothing
});
Lists.newArrayList(new ReportComputationSteps(computeEngineContainer).instances());
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.computation.task.step;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolderRule;
+import org.sonar.server.computation.task.projectanalysis.measure.Measure;
+import org.sonar.server.computation.task.projectanalysis.measure.MeasureRepositoryRule;
+import org.sonar.server.computation.task.projectanalysis.metric.MetricRepositoryRule;
+import org.sonar.server.computation.task.step.PostMeasuresComputationCheck.Context;
+import org.sonar.server.project.Project;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.sonar.api.measures.CoreMetrics.NCLOC;
+import static org.sonar.server.computation.task.projectanalysis.component.ReportComponent.DUMB_PROJECT;
+
+public class PostMeasuresComputationChecksStepTest {
+
+ @Rule
+ public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule().setRoot(DUMB_PROJECT);
+ @Rule
+ public MetricRepositoryRule metricRepository = new MetricRepositoryRule().add(NCLOC);
+ @Rule
+ public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
+ @Rule
+ public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void execute_extensions() {
+ PostMeasuresComputationCheck check1 = mock(PostMeasuresComputationCheck.class);
+ PostMeasuresComputationCheck check2 = mock(PostMeasuresComputationCheck.class);
+
+ newStep(check1, check2).execute();
+
+ InOrder inOrder = inOrder(check1, check2);
+ inOrder.verify(check1).onCheck(any(Context.class));
+ inOrder.verify(check2).onCheck(any(Context.class));
+ }
+
+ @Test
+ public void context_contains_project_uuid_from_analysis_metada_holder() {
+ analysisMetadataHolder.setProject(new Project("project_uuid", "project_key", "project_name"));
+ PostMeasuresComputationCheck check = mock(PostMeasuresComputationCheck.class);
+
+ newStep(check).execute();
+
+ ArgumentCaptor<Context> contextArgumentCaptor = ArgumentCaptor.forClass(Context.class);
+ verify(check).onCheck(contextArgumentCaptor.capture());
+ assertThat(contextArgumentCaptor.getValue().getProjectUuid()).isEqualTo("project_uuid");
+ }
+
+ @Test
+ public void context_contains_ncloc_when_available() {
+ PostMeasuresComputationCheck check = mock(PostMeasuresComputationCheck.class);
+ measureRepository.addRawMeasure(DUMB_PROJECT.getReportAttributes().getRef(), CoreMetrics.NCLOC_KEY, Measure.newMeasureBuilder().create(10));
+
+ newStep(check).execute();
+
+ ArgumentCaptor<Context> contextArgumentCaptor = ArgumentCaptor.forClass(Context.class);
+ verify(check).onCheck(contextArgumentCaptor.capture());
+ assertThat(contextArgumentCaptor.getValue().getNcloc()).isEqualTo(10);
+ }
+
+ @Test
+ public void ncloc_is_zero_in_context_when_not_available() {
+ PostMeasuresComputationCheck check = mock(PostMeasuresComputationCheck.class);
+
+ newStep(check).execute();
+
+ ArgumentCaptor<Context> contextArgumentCaptor = ArgumentCaptor.forClass(Context.class);
+ verify(check).onCheck(contextArgumentCaptor.capture());
+ assertThat(contextArgumentCaptor.getValue().getNcloc()).isEqualTo(0);
+ }
+
+ @Test
+ public void do_nothing_if_no_extensions() {
+ // no failure
+ newStep().execute();
+ }
+
+ @Test
+ public void fail_if_an_extension_throws_an_exception() {
+ PostMeasuresComputationCheck check1 = mock(PostMeasuresComputationCheck.class);
+ PostMeasuresComputationCheck check2 = mock(PostMeasuresComputationCheck.class);
+ doThrow(new IllegalStateException("BOOM")).when(check2).onCheck(any(Context.class));
+ PostMeasuresComputationCheck check3 = mock(PostMeasuresComputationCheck.class);
+
+ try {
+ newStep(check1, check2, check3).execute();
+ fail();
+ } catch (IllegalStateException e) {
+ assertThat(e).hasMessage("BOOM");
+ verify(check1).onCheck(any(Context.class));
+ verify(check3, never()).onCheck(any(Context.class));
+ }
+ }
+
+ @Test
+ public void test_getDescription() {
+ assertThat(newStep().getDescription()).isNotEmpty();
+ }
+
+ private PostMeasuresComputationChecksStep newStep(PostMeasuresComputationCheck... postMeasuresComputationChecks) {
+ if (postMeasuresComputationChecks.length == 0){
+ return new PostMeasuresComputationChecksStep(treeRootHolder, metricRepository, measureRepository, analysisMetadataHolder);
+ }
+ return new PostMeasuresComputationChecksStep(treeRootHolder, metricRepository, measureRepository, analysisMetadataHolder, postMeasuresComputationChecks);
+ }
+}