]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10684 Create internal API to block analysis based on ncloc
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 16 May 2018 15:37:57 +0000 (17:37 +0200)
committerSonarTech <sonartech@sonarsource.com>
Thu, 24 May 2018 18:20:48 +0000 (20:20 +0200)
server/sonar-db-dao/src/main/java/org/sonar/db/measure/LiveMeasureDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/measure/LiveMeasureMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/measure/SumNclocDbQuery.java [new file with mode: 0644]
server/sonar-db-dao/src/main/resources/org/sonar/db/measure/LiveMeasureMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/measure/LiveMeasureDaoTest.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/ReportComputationSteps.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/step/PostMeasuresComputationCheck.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/task/step/PostMeasuresComputationChecksStep.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportComputationStepsTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/step/PostMeasuresComputationChecksStepTest.java [new file with mode: 0644]

index 52cfdb646aec3d2cb11fc1f7b270544f6f65ccd4..dc07aa60d502deca9e2a53b7caa80bedbfe8ca38 100644 (file)
@@ -88,7 +88,11 @@ public class LiveMeasureDao implements Dao {
    * 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;
   }
 
index c0fca2f6a1ea71fea308d81e3044913dda8e6fa7..ed3977d684bf2290cc1308542c59c1358a76476d 100644 (file)
@@ -46,7 +46,8 @@ public interface LiveMeasureMapper {
   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,
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/measure/SumNclocDbQuery.java b/server/sonar-db-dao/src/main/java/org/sonar/db/measure/SumNclocDbQuery.java
new file mode 100644 (file)
index 0000000..b9e3a50
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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);
+    }
+  }
+}
index 6b571f5dc713b13bba5e4366c44a97f1734d710d..999aa79aec937d3fc43fdaccfcfaeb39cd4b2040 100644 (file)
@@ -41,7 +41,7 @@
     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 &lt;&gt; #{projectUuidToExclude,jdbcType=VARCHAR}
+    </if>
+    </where>
     group by b.project_uuid
     ) sumncloc
   </select>
index ed1b1e10e0f795d29306065120eedf4c97db020c..51d83d6d79566e00b464f3a25fd649b809e875bf 100644 (file)
@@ -211,9 +211,9 @@ public class LiveMeasureDaoTest {
     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));
@@ -234,6 +234,29 @@ public class LiveMeasureDaoTest {
     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);
index 8cb71963b0b72abcf3d5d9377d6902cdff02a0c4..852be83599eeeb775dd4cf80a69cf013c0c91721 100644 (file)
@@ -25,6 +25,7 @@ import org.sonar.server.computation.task.container.TaskContainer;
 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
@@ -70,6 +71,8 @@ public class ReportComputationSteps extends AbstractComputationSteps {
     LoadMeasureComputersStep.class,
     ExecuteVisitorsStep.class,
 
+    PostMeasuresComputationChecksStep.class,
+
     // Must be executed after computation of all measures
     ComputeMeasureVariationsStep.class,
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/step/PostMeasuresComputationCheck.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/step/PostMeasuresComputationCheck.java
new file mode 100644 (file)
index 0000000..2ba9c1c
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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();
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/step/PostMeasuresComputationChecksStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/step/PostMeasuresComputationChecksStep.java
new file mode 100644 (file)
index 0000000..844643f
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * 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;
+    }
+  }
+}
index 8e42d7818663245d7d7356dc0b425e8dbf2fefb4..3df6f260507ee6673896c0f90f02bec15177db86 100644 (file)
@@ -24,8 +24,6 @@ import org.junit.Rule;
 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;
@@ -39,11 +37,8 @@ public class ReportComputationStepsTest {
     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());
@@ -60,11 +55,8 @@ public class ReportComputationStepsTest {
         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());
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/step/PostMeasuresComputationChecksStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/step/PostMeasuresComputationChecksStepTest.java
new file mode 100644 (file)
index 0000000..b7bf64a
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * 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);
+  }
+}