Skip to content

Commit

Permalink
SONAR-9384 Fix computation of project without src but with test measures
Browse files Browse the repository at this point in the history
  • Loading branch information
julienlancelot authored and SonarTech committed May 2, 2018
1 parent 5c78e44 commit d13e2d9
Show file tree
Hide file tree
Showing 15 changed files with 459 additions and 20 deletions.
Expand Up @@ -22,7 +22,6 @@
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import org.sonar.server.computation.task.projectanalysis.component.Component; import org.sonar.server.computation.task.projectanalysis.component.Component;
import org.sonar.server.computation.task.projectanalysis.component.CrawlerDepthLimit;
import org.sonar.server.computation.task.projectanalysis.component.PathAwareCrawler; import org.sonar.server.computation.task.projectanalysis.component.PathAwareCrawler;
import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder; import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder;
import org.sonar.server.computation.task.projectanalysis.formula.Counter; import org.sonar.server.computation.task.projectanalysis.formula.Counter;
Expand All @@ -31,6 +30,7 @@
import org.sonar.server.computation.task.projectanalysis.formula.Formula; import org.sonar.server.computation.task.projectanalysis.formula.Formula;
import org.sonar.server.computation.task.projectanalysis.formula.FormulaExecutorComponentVisitor; import org.sonar.server.computation.task.projectanalysis.formula.FormulaExecutorComponentVisitor;
import org.sonar.server.computation.task.projectanalysis.formula.counter.IntSumCounter; import org.sonar.server.computation.task.projectanalysis.formula.counter.IntSumCounter;
import org.sonar.server.computation.task.projectanalysis.formula.counter.LongSumCounter;
import org.sonar.server.computation.task.projectanalysis.measure.Measure; 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.measure.MeasureRepository;
import org.sonar.server.computation.task.projectanalysis.metric.MetricRepository; import org.sonar.server.computation.task.projectanalysis.metric.MetricRepository;
Expand All @@ -42,20 +42,15 @@
import static org.sonar.api.measures.CoreMetrics.TEST_EXECUTION_TIME_KEY; import static org.sonar.api.measures.CoreMetrics.TEST_EXECUTION_TIME_KEY;
import static org.sonar.api.measures.CoreMetrics.TEST_FAILURES_KEY; import static org.sonar.api.measures.CoreMetrics.TEST_FAILURES_KEY;
import static org.sonar.api.measures.CoreMetrics.TEST_SUCCESS_DENSITY_KEY; import static org.sonar.api.measures.CoreMetrics.TEST_SUCCESS_DENSITY_KEY;
import static org.sonar.server.computation.task.projectanalysis.formula.SumFormula.createIntSumFormula;
import static org.sonar.server.computation.task.projectanalysis.formula.SumFormula.createLongSumFormula;


/** /**
* Computes unit test measures on files and then aggregates them on higher components. * Computes unit test measures on files and then aggregates them on higher components.
*/ */
public class UnitTestMeasuresStep implements ComputationStep { public class UnitTestMeasuresStep implements ComputationStep {


private static final String[] METRICS = new String[] {TESTS_KEY, TEST_ERRORS_KEY, TEST_FAILURES_KEY, TEST_SUCCESS_DENSITY_KEY}; private static final String[] METRICS = new String[] {TESTS_KEY, TEST_ERRORS_KEY, TEST_FAILURES_KEY, SKIPPED_TESTS_KEY, TEST_SUCCESS_DENSITY_KEY, TEST_EXECUTION_TIME_KEY};


private static final ImmutableList<Formula> FORMULAS = ImmutableList.of( private static final ImmutableList<Formula> FORMULAS = ImmutableList.of(new UnitTestsFormula());
createLongSumFormula(TEST_EXECUTION_TIME_KEY),
createIntSumFormula(SKIPPED_TESTS_KEY),
new UnitTestsFormula());


private final TreeRootHolder treeRootHolder; private final TreeRootHolder treeRootHolder;
private final MetricRepository metricRepository; private final MetricRepository metricRepository;
Expand Down Expand Up @@ -84,22 +79,34 @@ public UnitTestsCounter createNewCounter() {
@Override @Override
public Optional<Measure> createMeasure(UnitTestsCounter counter, CreateMeasureContext context) { public Optional<Measure> createMeasure(UnitTestsCounter counter, CreateMeasureContext context) {
String metricKey = context.getMetric().getKey(); String metricKey = context.getMetric().getKey();
Component leaf = counter.getLeaf();
switch (metricKey) { switch (metricKey) {
case TESTS_KEY: case TESTS_KEY:
return createMeasure(context.getComponent().getType(), counter.testsCounter.getValue()); return createIntMeasure(context.getComponent(), leaf, counter.testsCounter.getValue());
case TEST_ERRORS_KEY: case TEST_ERRORS_KEY:
return createMeasure(context.getComponent().getType(), counter.testsErrorsCounter.getValue()); return createIntMeasure(context.getComponent(), leaf, counter.testsErrorsCounter.getValue());
case TEST_FAILURES_KEY: case TEST_FAILURES_KEY:
return createMeasure(context.getComponent().getType(), counter.testsFailuresCounter.getValue()); return createIntMeasure(context.getComponent(), leaf, counter.testsFailuresCounter.getValue());
case SKIPPED_TESTS_KEY:
return createIntMeasure(context.getComponent(), leaf, counter.skippedTestsCounter.getValue());
case TEST_EXECUTION_TIME_KEY:
return createLongMeasure(context.getComponent(), leaf, counter.testExecutionTimeCounter.getValue());
case TEST_SUCCESS_DENSITY_KEY: case TEST_SUCCESS_DENSITY_KEY:
return createDensityMeasure(counter, context.getMetric().getDecimalScale()); return createDensityMeasure(counter, context.getMetric().getDecimalScale());
default: default:
throw new IllegalStateException(String.format("Metric '%s' is not supported", metricKey)); throw new IllegalStateException(String.format("Metric '%s' is not supported", metricKey));
} }
} }


private static Optional<Measure> createMeasure(Component.Type componentType, Optional<Integer> metricValue) { private static Optional<Measure> createIntMeasure(Component currentComponent, Component leafComponent, Optional<Integer> metricValue) {
if (metricValue.isPresent() && CrawlerDepthLimit.LEAVES.isDeeperThan(componentType)) { if (metricValue.isPresent() && leafComponent.getType().isDeeperThan(currentComponent.getType())) {
return Optional.of(Measure.newMeasureBuilder().create(metricValue.get()));
}
return Optional.absent();
}

private static Optional<Measure> createLongMeasure(Component currentComponent, Component leafComponent, Optional<Long> metricValue) {
if (metricValue.isPresent() && leafComponent.getType().isDeeperThan(currentComponent.getType())) {
return Optional.of(Measure.newMeasureBuilder().create(metricValue.get())); return Optional.of(Measure.newMeasureBuilder().create(metricValue.get()));
} }
return Optional.absent(); return Optional.absent();
Expand Down Expand Up @@ -133,19 +140,32 @@ private static class UnitTestsCounter implements Counter<UnitTestsCounter> {
private final IntSumCounter testsCounter = new IntSumCounter(TESTS_KEY); private final IntSumCounter testsCounter = new IntSumCounter(TESTS_KEY);
private final IntSumCounter testsErrorsCounter = new IntSumCounter(TEST_ERRORS_KEY); private final IntSumCounter testsErrorsCounter = new IntSumCounter(TEST_ERRORS_KEY);
private final IntSumCounter testsFailuresCounter = new IntSumCounter(TEST_FAILURES_KEY); private final IntSumCounter testsFailuresCounter = new IntSumCounter(TEST_FAILURES_KEY);
private final IntSumCounter skippedTestsCounter = new IntSumCounter(SKIPPED_TESTS_KEY);
private final LongSumCounter testExecutionTimeCounter = new LongSumCounter(TEST_EXECUTION_TIME_KEY);

private Component leaf;


@Override @Override
public void aggregate(UnitTestsCounter counter) { public void aggregate(UnitTestsCounter counter) {
testsCounter.aggregate(counter.testsCounter); testsCounter.aggregate(counter.testsCounter);
testsErrorsCounter.aggregate(counter.testsErrorsCounter); testsErrorsCounter.aggregate(counter.testsErrorsCounter);
testsFailuresCounter.aggregate(counter.testsFailuresCounter); testsFailuresCounter.aggregate(counter.testsFailuresCounter);
skippedTestsCounter.aggregate(counter.skippedTestsCounter);
testExecutionTimeCounter.aggregate(counter.testExecutionTimeCounter);
} }


@Override @Override
public void initialize(CounterInitializationContext context) { public void initialize(CounterInitializationContext context) {
this.leaf = context.getLeaf();
testsCounter.initialize(context); testsCounter.initialize(context);
testsErrorsCounter.initialize(context); testsErrorsCounter.initialize(context);
testsFailuresCounter.initialize(context); testsFailuresCounter.initialize(context);
skippedTestsCounter.initialize(context);
testExecutionTimeCounter.initialize(context);
}

Component getLeaf() {
return leaf;
} }
} }


Expand Down
Expand Up @@ -23,6 +23,7 @@
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.CoreMetrics;
import org.sonar.server.computation.task.projectanalysis.component.Component; import org.sonar.server.computation.task.projectanalysis.component.Component;
import org.sonar.server.computation.task.projectanalysis.component.PathAwareCrawler; import org.sonar.server.computation.task.projectanalysis.component.PathAwareCrawler;
Expand All @@ -48,6 +49,7 @@
import static org.sonar.server.computation.task.projectanalysis.measure.Measure.newMeasureBuilder; import static org.sonar.server.computation.task.projectanalysis.measure.Measure.newMeasureBuilder;
import static org.sonar.server.computation.task.projectanalysis.measure.MeasureRepoEntry.entryOf; import static org.sonar.server.computation.task.projectanalysis.measure.MeasureRepoEntry.entryOf;
import static org.sonar.server.computation.task.projectanalysis.measure.MeasureRepoEntry.toEntries; import static org.sonar.server.computation.task.projectanalysis.measure.MeasureRepoEntry.toEntries;
import static org.sonar.test.ExceptionCauseMatcher.hasType;


public class ReportFormulaExecutorComponentVisitorTest { public class ReportFormulaExecutorComponentVisitorTest {
private static final int ROOT_REF = 1; private static final int ROOT_REF = 1;
Expand Down Expand Up @@ -78,6 +80,8 @@ public class ReportFormulaExecutorComponentVisitorTest {
.build()) .build())
.build(); .build();


@Rule
public ExpectedException expectedException = ExpectedException.none();
@Rule @Rule
public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
@Rule @Rule
Expand Down Expand Up @@ -211,6 +215,83 @@ public void add_measure_even_when_leaf_is_not_FILE() {
assertAddedRawMeasure(DIRECTORY_1_REF, 0); assertAddedRawMeasure(DIRECTORY_1_REF, 0);
} }


@Test
public void compute_measure_on_project_without_children() {
ReportComponent root = builder(PROJECT, ROOT_REF).build();
treeRootHolder.setRoot(root);
measureRepository.addRawMeasure(ROOT_REF, LINES_KEY, newMeasureBuilder().create(10));

new PathAwareCrawler<>(formulaExecutorComponentVisitor(new FakeFormula()))
.visit(root);

assertThat(toEntries(measureRepository.getAddedRawMeasures(ROOT_REF))).containsOnly(entryOf(NCLOC_KEY, newMeasureBuilder().create(10)));
}

@Test
public void ignore_measure_defined_on_project_when_measure_is_defined_on_leaf() {
ReportComponent root = builder(PROJECT, ROOT_REF)
.addChildren(
builder(Component.Type.FILE, FILE_1_REF).build())
.build();
treeRootHolder.setRoot(root);
measureRepository.addRawMeasure(ROOT_REF, LINES_KEY, newMeasureBuilder().create(10));
measureRepository.addRawMeasure(FILE_1_REF, LINES_KEY, newMeasureBuilder().create(2));

new PathAwareCrawler<>(formulaExecutorComponentVisitor(new FakeFormula()))
.visit(root);

assertAddedRawMeasure(ROOT_REF, 2);
assertAddedRawMeasure(FILE_1_REF, 2);
}

@Test
public void fail_when_trying_to_compute_file_measure_already_existing_in_report() {
ReportComponent root = builder(PROJECT, ROOT_REF)
.addChildren(
builder(Component.Type.FILE, FILE_1_REF).build())
.build();
treeRootHolder.setRoot(root);
measureRepository.addRawMeasure(FILE_1_REF, NCLOC_KEY, newMeasureBuilder().create(2));

expectedException.expectCause(hasType(UnsupportedOperationException.class)
.andMessage(String.format("A measure can only be set once for Component (ref=%s), Metric (key=%s)", FILE_1_REF, NCLOC_KEY)));

new PathAwareCrawler<>(formulaExecutorComponentVisitor(new FakeFormula()))
.visit(root);
}

@Test
public void fail_on_project_without_children_already_having_computed_measure() {
ReportComponent root = builder(PROJECT, ROOT_REF).build();
treeRootHolder.setRoot(root);
measureRepository.addRawMeasure(ROOT_REF, NCLOC_KEY, newMeasureBuilder().create(10));

expectedException.expectCause(hasType(UnsupportedOperationException.class)
.andMessage(String.format("A measure can only be set once for Component (ref=%s), Metric (key=%s)", ROOT_REF, NCLOC_KEY)));

new PathAwareCrawler<>(formulaExecutorComponentVisitor(new FakeFormula()))
.visit(root);
}

@Test
public void fail_on_project_containing_module_without_children_already_having_computed_measure() {
ReportComponent root = builder(PROJECT, ROOT_REF)
.addChildren(
ReportComponent.builder(MODULE, MODULE_1_REF).build(),
builder(Component.Type.FILE, FILE_1_REF).build())
.build();
treeRootHolder.setRoot(root);
measureRepository.addRawMeasure(FILE_1_REF, LINES_KEY, newMeasureBuilder().create(10));
// Ncloc is already computed on module
measureRepository.addRawMeasure(MODULE_1_REF, NCLOC_KEY, newMeasureBuilder().create(3));

expectedException.expectCause(hasType(UnsupportedOperationException.class)
.andMessage(String.format("A measure can only be set once for Component (ref=%s), Metric (key=%s)", MODULE_1_REF, NCLOC_KEY)));

new PathAwareCrawler<>(formulaExecutorComponentVisitor(new FakeFormula()))
.visit(root);
}

private FormulaExecutorComponentVisitor formulaExecutorComponentVisitor(Formula formula) { private FormulaExecutorComponentVisitor formulaExecutorComponentVisitor(Formula formula) {
return FormulaExecutorComponentVisitor.newBuilder(metricRepository, measureRepository) return FormulaExecutorComponentVisitor.newBuilder(metricRepository, measureRepository)
.withVariationSupport(periodsHolder) .withVariationSupport(periodsHolder)
Expand Down
Expand Up @@ -113,7 +113,7 @@ public void aggregate_tests_execution_time() {
} }


@Test @Test
public void aggregate_skipped_tests_time() { public void aggregate_skipped_tests() {
checkMeasuresAggregation(SKIPPED_TESTS_KEY, 100, 400, 500); checkMeasuresAggregation(SKIPPED_TESTS_KEY, 100, 400, 500);
} }


Expand Down Expand Up @@ -258,6 +258,54 @@ public void do_not_compute_test_success_density_when_no_tests_in_failure() {
assertThat(measureRepository.getAddedRawMeasure(ROOT_REF, TEST_SUCCESS_DENSITY_KEY)).isAbsent(); assertThat(measureRepository.getAddedRawMeasure(ROOT_REF, TEST_SUCCESS_DENSITY_KEY)).isAbsent();
} }


@Test
public void aggregate_measures_when_tests_measures_are_defined_on_directory() {
treeRootHolder.setRoot(builder(PROJECT, ROOT_REF)
.addChildren(
builder(MODULE, MODULE_REF)
.addChildren(
builder(DIRECTORY, DIRECTORY_REF).build())
.build())
.build());
measureRepository.addRawMeasure(DIRECTORY_REF, TESTS_KEY, newMeasureBuilder().create(10));
measureRepository.addRawMeasure(DIRECTORY_REF, TEST_ERRORS_KEY, newMeasureBuilder().create(2));
measureRepository.addRawMeasure(DIRECTORY_REF, TEST_FAILURES_KEY, newMeasureBuilder().create(1));
measureRepository.addRawMeasure(DIRECTORY_REF, SKIPPED_TESTS_KEY, newMeasureBuilder().create(5));
measureRepository.addRawMeasure(DIRECTORY_REF, TEST_EXECUTION_TIME_KEY, newMeasureBuilder().create(100L));

underTest.execute();

assertThat(toEntries(measureRepository.getAddedRawMeasures(MODULE_REF))).containsOnly(
entryOf(TESTS_KEY, newMeasureBuilder().create(10)),
entryOf(TEST_ERRORS_KEY, newMeasureBuilder().create(2)),
entryOf(TEST_FAILURES_KEY, newMeasureBuilder().create(1)),
entryOf(SKIPPED_TESTS_KEY, newMeasureBuilder().create(5)),
entryOf(TEST_EXECUTION_TIME_KEY, newMeasureBuilder().create(100L)),
entryOf(TEST_SUCCESS_DENSITY_KEY, newMeasureBuilder().create(70d, 1)));
assertThat(toEntries(measureRepository.getAddedRawMeasures(ROOT_REF))).containsOnly(
entryOf(TESTS_KEY, newMeasureBuilder().create(10)),
entryOf(TEST_ERRORS_KEY, newMeasureBuilder().create(2)),
entryOf(TEST_FAILURES_KEY, newMeasureBuilder().create(1)),
entryOf(SKIPPED_TESTS_KEY, newMeasureBuilder().create(5)),
entryOf(TEST_EXECUTION_TIME_KEY, newMeasureBuilder().create(100L)),
entryOf(TEST_SUCCESS_DENSITY_KEY, newMeasureBuilder().create(70d, 1)));
}

@Test
public void compute_test_success_density_measure_when_tests_measures_are_defined_at_project_level_and_no_children() {
treeRootHolder.setRoot(builder(PROJECT, ROOT_REF).build());
measureRepository.addRawMeasure(ROOT_REF, TESTS_KEY, newMeasureBuilder().create(10));
measureRepository.addRawMeasure(ROOT_REF, TEST_ERRORS_KEY, newMeasureBuilder().create(2));
measureRepository.addRawMeasure(ROOT_REF, TEST_FAILURES_KEY, newMeasureBuilder().create(1));
measureRepository.addRawMeasure(ROOT_REF, SKIPPED_TESTS_KEY, newMeasureBuilder().create(5));
measureRepository.addRawMeasure(ROOT_REF, TEST_EXECUTION_TIME_KEY, newMeasureBuilder().create(100L));

underTest.execute();

assertThat(toEntries(measureRepository.getAddedRawMeasures(ROOT_REF))).containsOnly(
entryOf(TEST_SUCCESS_DENSITY_KEY, newMeasureBuilder().create(70d, 1)));
}

private void checkMeasuresAggregation(String metricKey, int file1Value, int file2Value, int expectedValue) { private void checkMeasuresAggregation(String metricKey, int file1Value, int file2Value, int expectedValue) {
measureRepository.addRawMeasure(FILE_1_REF, metricKey, newMeasureBuilder().create(file1Value)); measureRepository.addRawMeasure(FILE_1_REF, metricKey, newMeasureBuilder().create(file1Value));
measureRepository.addRawMeasure(FILE_2_REF, metricKey, newMeasureBuilder().create(file2Value)); measureRepository.addRawMeasure(FILE_2_REF, metricKey, newMeasureBuilder().create(file2Value));
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Expand Up @@ -50,6 +50,7 @@ include 'tests:plugins:posttask-plugin'
include 'tests:plugins:project-builder-plugin' include 'tests:plugins:project-builder-plugin'
include 'tests:plugins:property-relocation-plugin' include 'tests:plugins:property-relocation-plugin'
include 'tests:plugins:property-sets-plugin' include 'tests:plugins:property-sets-plugin'
include 'tests:plugins:save-measure-on-project-plugin'
include 'tests:plugins:security-plugin' include 'tests:plugins:security-plugin'
include 'tests:plugins:server-plugin' include 'tests:plugins:server-plugin'
include 'tests:plugins:settings-encryption-plugin' include 'tests:plugins:settings-encryption-plugin'
Expand Down
11 changes: 6 additions & 5 deletions tests/build.gradle
Expand Up @@ -11,6 +11,9 @@ configurations {
def pluginsForITs = [ def pluginsForITs = [
':plugins:sonar-xoo-plugin', ':plugins:sonar-xoo-plugin',
':tests:plugins:access-secured-props-plugin', ':tests:plugins:access-secured-props-plugin',
':tests:plugins:backdating-plugin-v1',
':tests:plugins:backdating-plugin-v2',
':tests:plugins:backdating-customplugin',
':tests:plugins:base-auth-plugin', ':tests:plugins:base-auth-plugin',
':tests:plugins:batch-plugin', ':tests:plugins:batch-plugin',
':tests:plugins:extension-lifecycle-plugin', ':tests:plugins:extension-lifecycle-plugin',
Expand All @@ -23,22 +26,20 @@ def pluginsForITs = [
':tests:plugins:l10n-fr-pack', ':tests:plugins:l10n-fr-pack',
':tests:plugins:license-plugin', ':tests:plugins:license-plugin',
':tests:plugins:oauth2-auth-plugin', ':tests:plugins:oauth2-auth-plugin',
':tests:plugins:posttask-plugin',
':tests:plugins:project-builder-plugin', ':tests:plugins:project-builder-plugin',
':tests:plugins:property-relocation-plugin', ':tests:plugins:property-relocation-plugin',
':tests:plugins:property-sets-plugin', ':tests:plugins:property-sets-plugin',
':tests:plugins:save-measure-on-project-plugin',
':tests:plugins:security-plugin', ':tests:plugins:security-plugin',
':tests:plugins:server-plugin', ':tests:plugins:server-plugin',
':tests:plugins:settings-encryption-plugin', ':tests:plugins:settings-encryption-plugin',
':tests:plugins:settings-plugin', ':tests:plugins:settings-plugin',
':tests:plugins:sonar-fake-plugin', ':tests:plugins:sonar-fake-plugin',
':tests:plugins:sonar-subcategories-plugin', ':tests:plugins:sonar-subcategories-plugin',
':tests:plugins:ui-extensions-plugin', ':tests:plugins:ui-extensions-plugin',
':tests:plugins:posttask-plugin',
':tests:plugins:wait-at-platform-level4-plugin', ':tests:plugins:wait-at-platform-level4-plugin',
':tests:plugins:ws-plugin', ':tests:plugins:ws-plugin'
':tests:plugins:backdating-plugin-v1',
':tests:plugins:backdating-plugin-v2',
':tests:plugins:backdating-customplugin'
] ]


dependencies { dependencies {
Expand Down
19 changes: 19 additions & 0 deletions tests/plugins/save-measure-on-project-plugin/build.gradle
@@ -0,0 +1,19 @@
dependencies {
compileOnly project(path: ':sonar-plugin-api', configuration: 'shadow')
}

jar {
manifest {
attributes(
'Plugin-Key': 'save-measure-on-project-plugin',
'Plugin-Version': version,
'Plugin-Class': 'org.sonar.measure.SaveMeasureOnModulePlugin',
'Plugin-ChildFirstClassLoader': 'false',
'Sonar-Version': version,
'SonarLint-Supported': 'false',
'Plugin-Name': 'Custom',
'Plugin-License': 'GNU LGPL 3',
)
}
}

0 comments on commit d13e2d9

Please sign in to comment.