From: Sébastien Lesaint
Date: Thu, 29 Aug 2019 13:27:53 +0000 (+0200)
Subject: use testFixtures instead of test configuration of ce-task-projectanalysis
X-Git-Tag: 8.0~172
X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=7a1b1d1728ae111910f2fc3eee6f6857f9bf526b;p=sonarqube.git
use testFixtures instead of test configuration of ce-task-projectanalysis
---
diff --git a/server/sonar-ce-task-projectanalysis/build.gradle b/server/sonar-ce-task-projectanalysis/build.gradle
index 99971d84553..b801b25a684 100644
--- a/server/sonar-ce-task-projectanalysis/build.gradle
+++ b/server/sonar-ce-task-projectanalysis/build.gradle
@@ -14,11 +14,6 @@ sourceSets {
}
}
-configurations {
- tests
- testCompile.extendsFrom compileOnly
-}
-
dependencies {
// please keep the list grouped by configuration and ordered by name
@@ -54,18 +49,14 @@ dependencies {
testCompile 'org.apache.logging.log4j:log4j-core'
testCompile 'org.assertj:assertj-core'
testCompile 'org.assertj:assertj-guava'
- testCompile 'org.mockito:mockito-core'
testCompile 'org.reflections:reflections'
testCompile project(':sonar-testing-harness')
- testCompile testFixtures(project(':server:sonar-ce-task'))
testCompile testFixtures(project(':server:sonar-server-common'))
-}
-task testJar(type: Jar) {
- classifier = 'tests'
- from sourceSets.test.output
-}
+ testFixturesApi 'junit:junit'
+ testFixturesApi 'org.assertj:assertj-core'
+ testFixturesApi 'org.mockito:mockito-core'
+ testFixturesApi testFixtures(project(':server:sonar-ce-task'))
-artifacts {
- tests testJar
+ testFixturesCompileOnly 'com.google.code.findbugs:jsr305'
}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderRule.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderRule.java
deleted file mode 100644
index 34a2e0f3657..00000000000
--- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderRule.java
+++ /dev/null
@@ -1,270 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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.ce.task.projectanalysis.analysis;
-
-import java.util.Date;
-import java.util.Map;
-import java.util.Optional;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.junit.rules.ExternalResource;
-import org.sonar.ce.task.util.InitializedProperty;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.server.project.Project;
-import org.sonar.server.qualityprofile.QualityProfile;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-import static java.util.Objects.requireNonNull;
-import static org.apache.commons.lang.StringUtils.defaultIfBlank;
-
-public class AnalysisMetadataHolderRule extends ExternalResource implements MutableAnalysisMetadataHolder {
-
- private final InitializedProperty organizationsEnabled = new InitializedProperty<>();
-
- private final InitializedProperty organization = new InitializedProperty<>();
-
- private final InitializedProperty uuid = new InitializedProperty<>();
-
- private final InitializedProperty analysisDate = new InitializedProperty<>();
-
- private final InitializedProperty baseAnalysis = new InitializedProperty<>();
-
- private final InitializedProperty crossProjectDuplicationEnabled = new InitializedProperty<>();
-
- private final InitializedProperty branch = new InitializedProperty<>();
-
- private final InitializedProperty pullRequestId = new InitializedProperty<>();
-
- private final InitializedProperty project = new InitializedProperty<>();
-
- private final InitializedProperty rootComponentRef = new InitializedProperty<>();
-
- private final InitializedProperty
- */
-public final class MeasureRepoEntry {
- private final String metricKey;
- private final Measure measure;
-
- public MeasureRepoEntry(String metricKey, Measure measure) {
- this.metricKey = metricKey;
- this.measure = measure;
- }
-
- public static Function, MeasureRepoEntry> toMeasureRepoEntry() {
- return EntryToMeasureRepoEntry.INSTANCE;
- }
-
- public static Iterable toEntries(SetMultimap data) {
- return FluentIterable.from(data.entries()).transform(toMeasureRepoEntry()).toList();
- }
-
- public static MeasureRepoEntry entryOf(String metricKey, Measure measure) {
- return new MeasureRepoEntry(metricKey, measure);
- }
-
- public static boolean deepEquals(Measure measure, Measure measure1) {
- return measure.getValueType() == measure1.getValueType()
- && equalsByValue(measure, measure1)
- && equalsByVariation(measure, measure1)
- && equalsByQualityGateStatus(measure, measure1)
- && Objects.equals(measure.getData(), measure1.getData());
- }
-
- private static boolean equalsByValue(Measure measure, Measure measure1) {
- switch (measure.getValueType()) {
- case BOOLEAN:
- return measure.getBooleanValue() == measure1.getBooleanValue();
- case INT:
- return measure.getIntValue() == measure1.getIntValue();
- case LONG:
- return measure.getLongValue() == measure1.getLongValue();
- case DOUBLE:
- return Double.compare(measure.getDoubleValue(), measure1.getDoubleValue()) == 0;
- case STRING:
- return measure.getStringValue().equals(measure1.getStringValue());
- case LEVEL:
- return measure.getLevelValue() == measure1.getLevelValue();
- case NO_VALUE:
- return true;
- default:
- throw new IllegalArgumentException("Unsupported ValueType " + measure.getValueType());
- }
- }
-
- private static boolean equalsByVariation(Measure measure, Measure measure1) {
- return measure.hasVariation() == measure1.hasVariation() && (!measure.hasVariation()
- || Double.compare(scale(measure.getVariation()), scale(measure1.getVariation())) == 0);
- }
-
- private static final int DOUBLE_PRECISION = 1;
-
- private static double scale(double value) {
- BigDecimal bd = BigDecimal.valueOf(value);
- return bd.setScale(DOUBLE_PRECISION, RoundingMode.HALF_UP).doubleValue();
- }
-
- private static boolean equalsByQualityGateStatus(Measure measure, Measure measure1) {
- if (measure.hasQualityGateStatus() != measure1.hasQualityGateStatus()) {
- return false;
- }
- if (!measure.hasQualityGateStatus()) {
- return true;
- }
- return Objects.equals(measure.getQualityGateStatus(), measure1.getQualityGateStatus());
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- MeasureRepoEntry that = (MeasureRepoEntry) o;
- return Objects.equals(metricKey, that.metricKey) &&
- deepEquals(measure, that.measure);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(metricKey, measure);
- }
-
- @Override
- public String toString() {
- return "<" + metricKey + ", " + measure + '>';
- }
-
- private enum EntryToMeasureRepoEntry implements Function, MeasureRepoEntry> {
- INSTANCE;
-
- @Nullable
- @Override
- public MeasureRepoEntry apply(@Nonnull Map.Entry input) {
- return new MeasureRepoEntry(input.getKey(), input.getValue());
- }
- }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/measure/MeasureRepositoryRule.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/measure/MeasureRepositoryRule.java
deleted file mode 100644
index 2c579151024..00000000000
--- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/measure/MeasureRepositoryRule.java
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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.ce.task.projectanalysis.measure;
-
-import com.google.common.base.Function;
-import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableSetMultimap;
-import com.google.common.collect.SetMultimap;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import org.junit.rules.ExternalResource;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.ComponentProvider;
-import org.sonar.ce.task.projectanalysis.component.NoComponentProvider;
-import org.sonar.ce.task.projectanalysis.component.TreeComponentProvider;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderComponentProvider;
-import org.sonar.ce.task.projectanalysis.metric.Metric;
-import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.collect.FluentIterable.from;
-import static com.google.common.collect.Maps.filterKeys;
-import static java.lang.String.format;
-import static java.util.Objects.requireNonNull;
-
-/**
- * An implementation of MeasureRepository as a JUnit rule which provides add methods for raw measures and extra add
- * methods that takes component ref and metric keys thanks to the integration with various Component and Metric
- * providers.
- */
-public class MeasureRepositoryRule extends ExternalResource implements MeasureRepository {
- private final ComponentProvider componentProvider;
- @CheckForNull
- private final MetricRepositoryRule metricRepositoryRule;
- private final Map baseMeasures = new HashMap<>();
- private final Map rawMeasures = new HashMap<>();
- private final Map initialRawMeasures = new HashMap<>();
- private final Predicate> isAddedMeasure = input -> !initialRawMeasures.containsKey(input.getKey())
- || !MeasureRepoEntry.deepEquals(input.getValue(), initialRawMeasures.get(input.getKey()));
-
- private MeasureRepositoryRule(ComponentProvider componentProvider, @Nullable MetricRepositoryRule metricRepositoryRule) {
- this.componentProvider = componentProvider;
- this.metricRepositoryRule = metricRepositoryRule;
- }
-
- @Override
- protected void after() {
- componentProvider.reset();
- baseMeasures.clear();
- rawMeasures.clear();
- }
-
- public static MeasureRepositoryRule create() {
- return new MeasureRepositoryRule(NoComponentProvider.INSTANCE, null);
- }
-
- public static MeasureRepositoryRule create(TreeRootHolder treeRootHolder, MetricRepositoryRule metricRepositoryRule) {
- return new MeasureRepositoryRule(new TreeRootHolderComponentProvider(treeRootHolder), requireNonNull(metricRepositoryRule));
- }
-
- public static MeasureRepositoryRule create(Component treeRoot, MetricRepositoryRule metricRepositoryRule) {
- return new MeasureRepositoryRule(new TreeComponentProvider(treeRoot), requireNonNull(metricRepositoryRule));
- }
-
- public MeasureRepositoryRule addBaseMeasure(int componentRef, String metricKey, Measure measure) {
- checkAndInitProvidersState();
-
- InternalKey internalKey = new InternalKey(componentProvider.getByRef(componentRef), metricRepositoryRule.getByKey(metricKey));
- checkState(!baseMeasures.containsKey(internalKey), format("Can not add a BaseMeasure twice for a Component (ref=%s) and Metric (key=%s)", componentRef, metricKey));
-
- baseMeasures.put(internalKey, measure);
-
- return this;
- }
-
- public SetMultimap getRawMeasures(int componentRef) {
- return getRawMeasures(componentProvider.getByRef(componentRef));
- }
-
- /**
- * Return measures that were added by the step (using {@link #add(Component, Metric, Measure)}).
- * It does not contain the one added in the test by {@link #addRawMeasure(int, String, Measure)}
- */
- public SetMultimap getAddedRawMeasures(int componentRef) {
- checkAndInitProvidersState();
-
- return getAddedRawMeasures(componentProvider.getByRef(componentRef));
- }
-
- /**
- * Return a measure that were added by the step (using {@link #add(Component, Metric, Measure)}).
- * It does not contain the one added in the test by {@link #addRawMeasure(int, String, Measure)}
- */
- public Optional getAddedRawMeasure(Component component, String metricKey) {
- return getAddedRawMeasure(component.getReportAttributes().getRef(), metricKey);
- }
-
- /**
- * Return a measure that were added by the step (using {@link #add(Component, Metric, Measure)}).
- * It does not contain the one added in the test by {@link #addRawMeasure(int, String, Measure)}
- */
- public Optional getAddedRawMeasure(int componentRef, String metricKey) {
- checkAndInitProvidersState();
-
- Set measures = getAddedRawMeasures(componentProvider.getByRef(componentRef)).get(metricKey);
- if (measures.isEmpty()) {
- return Optional.empty();
- }
- checkArgument(measures.size() == 1, String.format("There is more than one measure on metric '%s' for component '%s'", metricKey, componentRef));
- return Optional.of(measures.iterator().next());
- }
-
- /**
- * Return measures that were added by the step (using {@link #add(Component, Metric, Measure)}).
- * It does not contain the one added in the test by {@link #addRawMeasure(int, String, Measure)}
- */
- public SetMultimap getAddedRawMeasures(Component component) {
- checkAndInitProvidersState();
-
- ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder();
- for (Map.Entry entry : from(filterKeys(rawMeasures, hasComponentRef(component)).entrySet()).filter(isAddedMeasure)) {
- builder.put(entry.getKey().getMetricKey(), entry.getValue());
- }
- return builder.build();
- }
-
- public MeasureRepositoryRule addRawMeasure(int componentRef, String metricKey, Measure measure) {
- checkAndInitProvidersState();
-
- InternalKey internalKey = new InternalKey(componentProvider.getByRef(componentRef), metricRepositoryRule.getByKey(metricKey));
- checkState(!rawMeasures.containsKey(internalKey), format(
- "A measure can only be set once for Component (ref=%s), Metric (key=%s)",
- componentRef, metricKey));
-
- rawMeasures.put(internalKey, measure);
- initialRawMeasures.put(internalKey, measure);
-
- return this;
- }
-
- @Override
- public Optional getBaseMeasure(Component component, Metric metric) {
- return Optional.ofNullable(baseMeasures.get(new InternalKey(component, metric)));
- }
-
- @Override
- public Optional getRawMeasure(Component component, Metric metric) {
- return Optional.ofNullable(rawMeasures.get(new InternalKey(component, metric)));
- }
-
- @Override
- public Set getRawMeasures(Component component, Metric metric) {
- return from(filterKeys(rawMeasures, hasComponentRef(component)).entrySet()).filter(new MatchMetric(metric)).transform(ToMeasure.INSTANCE).toSet();
- }
-
- @Override
- public SetMultimap getRawMeasures(Component component) {
- ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder();
- for (Map.Entry entry : filterKeys(rawMeasures, hasComponentRef(component)).entrySet()) {
- builder.put(entry.getKey().getMetricKey(), entry.getValue());
- }
- return builder.build();
- }
-
- private HasComponentRefPredicate hasComponentRef(Component component) {
- return new HasComponentRefPredicate(component);
- }
-
- @Override
- public void add(Component component, Metric metric, Measure measure) {
- String ref = getRef(component);
- InternalKey internalKey = new InternalKey(ref, metric.getKey());
- if (rawMeasures.containsKey(internalKey)) {
- throw new UnsupportedOperationException(format(
- "A measure can only be set once for Component (ref=%s), Metric (key=%s)",
- ref, metric.getKey()));
- }
- rawMeasures.put(internalKey, measure);
- }
-
- @Override
- public void update(Component component, Metric metric, Measure measure) {
- String componentRef = getRef(component);
- InternalKey internalKey = new InternalKey(componentRef, metric.getKey());
- if (!rawMeasures.containsKey(internalKey)) {
- throw new UnsupportedOperationException(format(
- "A measure can only be updated if it has been added first for Component (ref=%s), Metric (key=%s)",
- componentRef, metric.getKey()));
- }
- rawMeasures.put(internalKey, measure);
- }
-
- private void checkAndInitProvidersState() {
- checkState(metricRepositoryRule != null, "Can not add a measure by metric key if MeasureRepositoryRule has not been created for a MetricRepository");
- componentProvider.ensureInitialized();
- }
-
- public boolean isEmpty() {
- return rawMeasures.isEmpty();
- }
-
- private static final class InternalKey {
- private final String componentRef;
- private final String metricKey;
-
- public InternalKey(Component component, Metric metric) {
- this(getRef(component), metric.getKey());
- }
-
- private InternalKey(String componentRef, String metricKey) {
- this.componentRef = componentRef;
- this.metricKey = metricKey;
- }
-
- public String getComponentRef() {
- return componentRef;
- }
-
- public String getMetricKey() {
- return metricKey;
- }
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- InternalKey that = (InternalKey) o;
- return Objects.equals(componentRef, that.componentRef) &&
- Objects.equals(metricKey, that.metricKey);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(componentRef, metricKey);
- }
-
- @Override
- public String toString() {
- return "InternalKey{" +
- "component=" + componentRef +
- ", metric='" + metricKey + '\'' +
- '}';
- }
- }
-
- private static class HasComponentRefPredicate implements Predicate {
-
- private final String componentRef;
-
- public HasComponentRefPredicate(Component component) {
- this.componentRef = getRef(component);
- }
-
- @Override
- public boolean apply(@Nonnull InternalKey input) {
- return input.getComponentRef().equals(this.componentRef);
- }
- }
-
- private static String getRef(Component component) {
- return component.getType().isReportType() ? String.valueOf(component.getReportAttributes().getRef()) : component.getDbKey();
- }
-
- private static class MatchMetric implements Predicate> {
- private final Metric metric;
-
- public MatchMetric(Metric metric) {
- this.metric = metric;
- }
-
- @Override
- public boolean apply(@Nonnull Map.Entry input) {
- return input.getKey().getMetricKey().equals(metric.getKey());
- }
- }
-
- private enum ToMeasure implements Function, Measure> {
- INSTANCE;
-
- @Nullable
- @Override
- public Measure apply(@Nonnull Map.Entry input) {
- return input.getValue();
- }
- }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/metric/MetricRepositoryRule.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/metric/MetricRepositoryRule.java
deleted file mode 100644
index 7f781ff34b0..00000000000
--- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/metric/MetricRepositoryRule.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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.ce.task.projectanalysis.metric;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import org.junit.rules.ExternalResource;
-
-import static com.google.common.base.Preconditions.checkState;
-import static java.lang.String.format;
-import static java.util.Objects.requireNonNull;
-
-public class MetricRepositoryRule extends ExternalResource implements MetricRepository {
- private final Map metricsByKey = new HashMap<>();
- private final Map metricsById = new HashMap<>();
-
- /**
- * Convenience method to add a {@link Metric} to the repository created from a {@link org.sonar.api.measures.Metric},
- * most of the time it will be a constant of the {@link org.sonar.api.measures.CoreMetrics} class.
- *
- * For the id of the created metric, this method uses the hashCode of the metric's key. If you want to specify
- * the id of the create {@link Metric}, use {@link #add(int, org.sonar.api.measures.Metric)}
- *
- */
- public MetricRepositoryRule add(org.sonar.api.measures.Metric> coreMetric) {
- add(from(coreMetric));
- return this;
- }
-
- /**
- * Convenience method to add a {@link Metric} to the repository created from a {@link org.sonar.api.measures.Metric}
- * and with the specified id, most of the time it will be a constant of the {@link org.sonar.api.measures.CoreMetrics}
- * class.
- */
- public MetricRepositoryRule add(int id, org.sonar.api.measures.Metric> coreMetric) {
- add(from(id, coreMetric));
- return this;
- }
-
- private static Metric from(org.sonar.api.measures.Metric> coreMetric) {
- return from(coreMetric.getKey().hashCode(), coreMetric);
- }
-
- private static Metric from(int id, org.sonar.api.measures.Metric> coreMetric) {
- return new MetricImpl(
- id, coreMetric.getKey(), coreMetric.getName(),
- convert(coreMetric.getType()),
- coreMetric.getDecimalScale(),
- coreMetric.getBestValue(), coreMetric.isOptimizedBestValue());
- }
-
- private static Metric.MetricType convert(org.sonar.api.measures.Metric.ValueType coreMetricType) {
- return Metric.MetricType.valueOf(coreMetricType.name());
- }
-
- public MetricRepositoryRule add(Metric metric) {
- requireNonNull(metric.getKey(), "key can not be null");
- requireNonNull(metric.getId(), "id can not be null");
-
- checkState(!metricsByKey.containsKey(metric.getKey()), format("Repository already contains a metric for key %s", metric.getKey()));
- checkState(!metricsById.containsKey((long) metric.getId()), format("Repository already contains a metric for id %s", metric.getId()));
-
- metricsByKey.put(metric.getKey(), metric);
- metricsById.put((long) metric.getId(), metric);
-
- return this;
- }
-
- @Override
- protected void after() {
- this.metricsById.clear();
- this.metricsById.clear();
- }
-
- @Override
- public Metric getByKey(String key) {
- Metric res = metricsByKey.get(key);
- checkState(res != null, format("No Metric can be found for key %s", key));
- return res;
- }
-
- @Override
- public Metric getById(long id) {
- Metric res = metricsById.get(id);
- checkState(res != null, format("No Metric can be found for id %s", id));
- return res;
- }
-
- @Override
- public Optional getOptionalById(long id) {
- return Optional.of(metricsById.get(id));
- }
-
- @Override
- public Iterable getAll() {
- return metricsByKey.values();
- }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/BaseStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/BaseStepTest.java
deleted file mode 100644
index 16aed8003df..00000000000
--- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/BaseStepTest.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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.ce.task.projectanalysis.step;
-
-import org.junit.Test;
-import org.sonar.ce.task.step.ComputationStep;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * Temporary solution to test metadata. Should be replaced by a medium test of
- * all computation stack
- */
-public abstract class BaseStepTest {
-
- protected abstract ComputationStep step();
-
- @Test
- public void test_metadata() {
- assertThat(step().toString()).isNotEmpty();
- assertThat(step().getDescription()).isNotEmpty();
- }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderRule.java b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderRule.java
new file mode 100644
index 00000000000..34a2e0f3657
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/analysis/AnalysisMetadataHolderRule.java
@@ -0,0 +1,270 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ce.task.projectanalysis.analysis;
+
+import java.util.Date;
+import java.util.Map;
+import java.util.Optional;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.junit.rules.ExternalResource;
+import org.sonar.ce.task.util.InitializedProperty;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.server.project.Project;
+import org.sonar.server.qualityprofile.QualityProfile;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+import static org.apache.commons.lang.StringUtils.defaultIfBlank;
+
+public class AnalysisMetadataHolderRule extends ExternalResource implements MutableAnalysisMetadataHolder {
+
+ private final InitializedProperty organizationsEnabled = new InitializedProperty<>();
+
+ private final InitializedProperty organization = new InitializedProperty<>();
+
+ private final InitializedProperty uuid = new InitializedProperty<>();
+
+ private final InitializedProperty analysisDate = new InitializedProperty<>();
+
+ private final InitializedProperty baseAnalysis = new InitializedProperty<>();
+
+ private final InitializedProperty crossProjectDuplicationEnabled = new InitializedProperty<>();
+
+ private final InitializedProperty branch = new InitializedProperty<>();
+
+ private final InitializedProperty pullRequestId = new InitializedProperty<>();
+
+ private final InitializedProperty project = new InitializedProperty<>();
+
+ private final InitializedProperty rootComponentRef = new InitializedProperty<>();
+
+ private final InitializedProperty> qProfilesPerLanguage = new InitializedProperty<>();
+
+ private final InitializedProperty> pluginsByKey = new InitializedProperty<>();
+
+ private final InitializedProperty scmRevision = new InitializedProperty<>();
+
+ @Override
+ public AnalysisMetadataHolderRule setOrganizationsEnabled(boolean isOrganizationsEnabled) {
+ this.organizationsEnabled.setProperty(isOrganizationsEnabled);
+ return this;
+ }
+
+ @Override
+ public boolean isOrganizationsEnabled() {
+ checkState(organizationsEnabled.isInitialized(), "Organizations enabled flag has not been set");
+ return organizationsEnabled.getProperty();
+ }
+
+ @Override
+ public AnalysisMetadataHolderRule setOrganization(Organization organization) {
+ requireNonNull(organization, "organization can't be null");
+ this.organization.setProperty(organization);
+ return this;
+ }
+
+ public AnalysisMetadataHolderRule setOrganizationUuid(String uuid, String defaultQualityGateUuid) {
+ requireNonNull(uuid, "organization uuid can't be null");
+ this.organization
+ .setProperty(Organization.from(new OrganizationDto().setUuid(uuid).setKey("key_" + uuid).setName("name_" + uuid).setDefaultQualityGateUuid(defaultQualityGateUuid)));
+ return this;
+ }
+
+ @Override
+ public Organization getOrganization() {
+ checkState(organization.isInitialized(), "Organization has not been set");
+ return this.organization.getProperty();
+ }
+
+ @Override
+ public AnalysisMetadataHolderRule setUuid(String s) {
+ checkNotNull(s, "UUID must not be null");
+ this.uuid.setProperty(s);
+ return this;
+ }
+
+ @Override
+ public String getUuid() {
+ checkState(uuid.isInitialized(), "Analysis UUID has not been set");
+ return this.uuid.getProperty();
+ }
+
+ public AnalysisMetadataHolderRule setAnalysisDate(Date date) {
+ checkNotNull(date, "Date must not be null");
+ this.analysisDate.setProperty(date.getTime());
+ return this;
+ }
+
+ @Override
+ public AnalysisMetadataHolderRule setAnalysisDate(long date) {
+ checkNotNull(date, "Date must not be null");
+ this.analysisDate.setProperty(date);
+ return this;
+ }
+
+ @Override
+ public long getAnalysisDate() {
+ checkState(analysisDate.isInitialized(), "Analysis date has not been set");
+ return this.analysisDate.getProperty();
+ }
+
+ @Override
+ public boolean hasAnalysisDateBeenSet() {
+ return analysisDate.isInitialized();
+ }
+
+ @Override
+ public boolean isFirstAnalysis() {
+ return getBaseAnalysis() == null;
+ }
+
+ @Override
+ public AnalysisMetadataHolderRule setBaseAnalysis(@Nullable Analysis baseAnalysis) {
+ this.baseAnalysis.setProperty(baseAnalysis);
+ return this;
+ }
+
+ @Override
+ @CheckForNull
+ public Analysis getBaseAnalysis() {
+ checkState(baseAnalysis.isInitialized(), "Base analysis has not been set");
+ return baseAnalysis.getProperty();
+ }
+
+ @Override
+ public AnalysisMetadataHolderRule setCrossProjectDuplicationEnabled(boolean isCrossProjectDuplicationEnabled) {
+ this.crossProjectDuplicationEnabled.setProperty(isCrossProjectDuplicationEnabled);
+ return this;
+ }
+
+ @Override
+ public boolean isCrossProjectDuplicationEnabled() {
+ checkState(crossProjectDuplicationEnabled.isInitialized(), "Cross project duplication flag has not been set");
+ return crossProjectDuplicationEnabled.getProperty();
+ }
+
+ @Override
+ public AnalysisMetadataHolderRule setBranch(Branch branch) {
+ this.branch.setProperty(branch);
+ return this;
+ }
+
+ @Override
+ public Branch getBranch() {
+ checkState(branch.isInitialized(), "Branch has not been set");
+ return branch.getProperty();
+ }
+
+ @Override
+ public MutableAnalysisMetadataHolder setPullRequestKey(String pullRequestKey) {
+ this.pullRequestId.setProperty(pullRequestKey);
+ return this;
+ }
+
+ @Override
+ public String getPullRequestKey() {
+ checkState(pullRequestId.isInitialized(), "Pull request id has not been set");
+ return pullRequestId.getProperty();
+ }
+
+ @Override
+ public AnalysisMetadataHolderRule setProject(Project p) {
+ this.project.setProperty(p);
+ return this;
+ }
+
+ @Override
+ public Project getProject() {
+ checkState(project.isInitialized(), "Project has not been set");
+ return project.getProperty();
+ }
+
+ @Override
+ public AnalysisMetadataHolderRule setRootComponentRef(int rootComponentRef) {
+ this.rootComponentRef.setProperty(rootComponentRef);
+ return this;
+ }
+
+ @Override
+ public int getRootComponentRef() {
+ checkState(rootComponentRef.isInitialized(), "Root component ref has not been set");
+ return rootComponentRef.getProperty();
+ }
+
+ @Override
+ public AnalysisMetadataHolderRule setQProfilesByLanguage(Map qProfilesPerLanguage) {
+ this.qProfilesPerLanguage.setProperty(qProfilesPerLanguage);
+ return this;
+ }
+
+ @Override
+ public Map getQProfilesByLanguage() {
+ checkState(qProfilesPerLanguage.isInitialized(), "QProfile per language has not been set");
+ return qProfilesPerLanguage.getProperty();
+ }
+
+ @Override
+ public AnalysisMetadataHolderRule setScannerPluginsByKey(Map plugins) {
+ this.pluginsByKey.setProperty(plugins);
+ return this;
+ }
+
+ @Override
+ public Map getScannerPluginsByKey() {
+ checkState(pluginsByKey.isInitialized(), "Plugins per key has not been set");
+ return pluginsByKey.getProperty();
+ }
+
+ @Override
+ public MutableAnalysisMetadataHolder setScmRevision(@Nullable String s) {
+ checkState(!this.scmRevision.isInitialized(), "ScmRevisionId has already been set");
+ this.scmRevision.setProperty(defaultIfBlank(s, null));
+ return this;
+ }
+
+ @Override
+ public Optional getScmRevision() {
+ if (!scmRevision.isInitialized()) {
+ return Optional.empty();
+ }
+ return Optional.ofNullable(scmRevision.getProperty());
+ }
+
+ @Override
+ public boolean isShortLivingBranch() {
+ Branch property = this.branch.getProperty();
+ return property != null && property.getType() == BranchType.SHORT;
+ }
+
+ @Override
+ public boolean isLongLivingBranch() {
+ Branch property = this.branch.getProperty();
+ return property != null && property.getType() == BranchType.LONG;
+ }
+
+ @Override
+ public boolean isPullRequest() {
+ Branch property = this.branch.getProperty();
+ return property != null && property.getType() == BranchType.PULL_REQUEST;
+ }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/analysis/MutableAnalysisMetadataHolderRule.java b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/analysis/MutableAnalysisMetadataHolderRule.java
new file mode 100644
index 00000000000..a622ff7cdb5
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/analysis/MutableAnalysisMetadataHolderRule.java
@@ -0,0 +1,211 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ce.task.projectanalysis.analysis;
+
+import java.util.Map;
+import java.util.Optional;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.junit.rules.ExternalResource;
+import org.sonar.core.platform.PlatformEditionProvider;
+import org.sonar.server.project.Project;
+import org.sonar.server.qualityprofile.QualityProfile;
+
+import static org.mockito.Mockito.mock;
+
+public class MutableAnalysisMetadataHolderRule extends ExternalResource implements MutableAnalysisMetadataHolder {
+
+ private PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class);
+ private AnalysisMetadataHolderImpl delegate = new AnalysisMetadataHolderImpl(editionProvider);
+
+ @Override
+ protected void after() {
+ delegate = new AnalysisMetadataHolderImpl(editionProvider);
+ }
+
+ @Override
+ public boolean isOrganizationsEnabled() {
+ return delegate.isOrganizationsEnabled();
+ }
+
+ @Override
+ public MutableAnalysisMetadataHolderRule setOrganizationsEnabled(boolean isOrganizationsEnabled) {
+ delegate.setOrganizationsEnabled(isOrganizationsEnabled);
+ return this;
+ }
+
+ @Override
+ public MutableAnalysisMetadataHolderRule setOrganization(Organization organization) {
+ delegate.setOrganization(organization);
+ return this;
+ }
+
+ @Override
+ public Organization getOrganization() {
+ return delegate.getOrganization();
+ }
+
+ public MutableAnalysisMetadataHolderRule setUuid(String s) {
+ delegate.setUuid(s);
+ return this;
+ }
+
+ @Override
+ public String getUuid() {
+ return delegate.getUuid();
+ }
+
+ public MutableAnalysisMetadataHolderRule setAnalysisDate(long date) {
+ delegate.setAnalysisDate(date);
+ return this;
+ }
+
+ @Override
+ public long getAnalysisDate() {
+ return delegate.getAnalysisDate();
+ }
+
+ @Override
+ public boolean hasAnalysisDateBeenSet() {
+ return delegate.hasAnalysisDateBeenSet();
+ }
+
+ @Override
+ public boolean isFirstAnalysis() {
+ return delegate.isFirstAnalysis();
+ }
+
+ @Override
+ public MutableAnalysisMetadataHolderRule setBaseAnalysis(@Nullable Analysis baseAnalysis) {
+ delegate.setBaseAnalysis(baseAnalysis);
+ return this;
+ }
+
+ @Override
+ @CheckForNull
+ public Analysis getBaseAnalysis() {
+ return delegate.getBaseAnalysis();
+ }
+
+ @Override
+ public boolean isCrossProjectDuplicationEnabled() {
+ return delegate.isCrossProjectDuplicationEnabled();
+ }
+
+ @Override
+ public MutableAnalysisMetadataHolderRule setCrossProjectDuplicationEnabled(boolean isCrossProjectDuplicationEnabled) {
+ delegate.setCrossProjectDuplicationEnabled(isCrossProjectDuplicationEnabled);
+ return this;
+ }
+
+ @Override
+ public Branch getBranch() {
+ return delegate.getBranch();
+ }
+
+ @Override
+ public MutableAnalysisMetadataHolderRule setBranch(Branch branch) {
+ delegate.setBranch(branch);
+ return this;
+ }
+
+ @Override
+ public String getPullRequestKey() {
+ return delegate.getPullRequestKey();
+ }
+
+ @Override
+ public MutableAnalysisMetadataHolder setPullRequestKey(String pullRequestKey) {
+ delegate.setPullRequestKey(pullRequestKey);
+ return this;
+ }
+
+ @Override
+ public MutableAnalysisMetadataHolderRule setProject(@Nullable Project project) {
+ delegate.setProject(project);
+ return this;
+ }
+
+ @Override
+ public Project getProject() {
+ return delegate.getProject();
+ }
+
+ @Override
+ public MutableAnalysisMetadataHolderRule setRootComponentRef(int rootComponentRef) {
+ delegate.setRootComponentRef(rootComponentRef);
+ return this;
+ }
+
+ @Override
+ public int getRootComponentRef() {
+ return delegate.getRootComponentRef();
+ }
+
+ @Override
+ public MutableAnalysisMetadataHolder setQProfilesByLanguage(Map qprofilesByLanguage) {
+ delegate.setQProfilesByLanguage(qprofilesByLanguage);
+ return this;
+ }
+
+ @Override
+ public Map getQProfilesByLanguage() {
+ return delegate.getQProfilesByLanguage();
+ }
+
+ @Override
+ public MutableAnalysisMetadataHolder setScannerPluginsByKey(Map plugins) {
+ delegate.setScannerPluginsByKey(plugins);
+ return this;
+ }
+
+ @Override
+ public Map getScannerPluginsByKey() {
+ return delegate.getScannerPluginsByKey();
+ }
+
+
+
+ @Override
+ public MutableAnalysisMetadataHolder setScmRevision(String scmRevisionId) {
+ delegate.setScmRevision(scmRevisionId);
+ return this;
+ }
+
+ @Override
+ public Optional getScmRevision() {
+ return delegate.getScmRevision();
+ }
+
+ @Override
+ public boolean isShortLivingBranch() {
+ return delegate.isShortLivingBranch();
+ }
+
+ @Override
+ public boolean isLongLivingBranch() {
+ return delegate.isLongLivingBranch();
+ }
+
+ @Override
+ public boolean isPullRequest() {
+ return delegate.isPullRequest();
+ }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/AbstractComponentProvider.java b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/AbstractComponentProvider.java
new file mode 100644
index 00000000000..b45de74bc26
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/AbstractComponentProvider.java
@@ -0,0 +1,52 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ce.task.projectanalysis.component;
+
+import static com.google.common.base.Preconditions.checkState;
+
+abstract class AbstractComponentProvider implements ComponentProvider {
+ private boolean initialized = false;
+
+ @Override
+ public void ensureInitialized() {
+ if (!this.initialized) {
+ ensureInitializedImpl();
+ this.initialized = true;
+ }
+ }
+
+ protected abstract void ensureInitializedImpl();
+
+ @Override
+ public void reset() {
+ resetImpl();
+ this.initialized = false;
+ }
+
+ protected abstract void resetImpl();
+
+ @Override
+ public Component getByRef(int componentRef) {
+ checkState(this.initialized, "%s has not been initialized", getClass().getSimpleName());
+ return getByRefImpl(componentRef);
+ }
+
+ protected abstract Component getByRefImpl(int componentRef);
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/ComponentProvider.java b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/ComponentProvider.java
new file mode 100644
index 00000000000..7d042647b07
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/ComponentProvider.java
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ce.task.projectanalysis.component;
+
+public interface ComponentProvider {
+ /**
+ * does nothing if already initialized
+ */
+ void ensureInitialized();
+
+ void reset();
+
+ /**
+ * @throws IllegalStateException if no component is found for the specified ref
+ * @throws IllegalStateException if provider has not been initialized
+ */
+ Component getByRef(int componentRef);
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/MutableTreeRootHolderRule.java b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/MutableTreeRootHolderRule.java
new file mode 100644
index 00000000000..506a4e2ee2e
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/MutableTreeRootHolderRule.java
@@ -0,0 +1,28 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ce.task.projectanalysis.component;
+
+public class MutableTreeRootHolderRule extends TreeRootHolderRule implements MutableTreeRootHolder {
+ @Override
+ public MutableTreeRootHolderRule setRoots(Component root, Component reportRoot) {
+ delegate.setRoots(root, reportRoot);
+ return this;
+ }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/NoComponentProvider.java b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/NoComponentProvider.java
new file mode 100644
index 00000000000..670b57d2efd
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/NoComponentProvider.java
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ce.task.projectanalysis.component;
+
+public enum NoComponentProvider implements ComponentProvider {
+ INSTANCE;
+
+ private static final String ERROR_MSG = "Can not add a measure by Component ref if MeasureRepositoryRule has not been created for some Component provider";
+
+ @Override
+ public void ensureInitialized() {
+ throw new IllegalStateException(ERROR_MSG);
+ }
+
+ @Override
+ public void reset() {
+ // do nothing
+ }
+
+ @Override
+ public Component getByRef(int componentRef) {
+ throw new IllegalStateException(ERROR_MSG);
+ }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/ReportComponent.java b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/ReportComponent.java
new file mode 100644
index 00000000000..0b10c06c7bb
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/ReportComponent.java
@@ -0,0 +1,304 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ce.task.projectanalysis.component;
+
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Arrays.asList;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Implementation of {@link Component} to unit test report components.
+ */
+public class ReportComponent implements Component {
+
+ private static final FileAttributes DEFAULT_FILE_ATTRIBUTES = new FileAttributes(false, null, 1);
+
+ public static final Component DUMB_PROJECT = builder(Type.PROJECT, 1)
+ .setKey("PROJECT_KEY")
+ .setPublicKey("PUBLIC_PROJECT_KEY")
+ .setUuid("PROJECT_UUID")
+ .setName("Project Name")
+ .setProjectVersion("1.0-SNAPSHOT")
+ .build();
+
+ private final Type type;
+ private final Status status;
+ private final String name;
+ private final String shortName;
+ @CheckForNull
+ private final String description;
+ private final String key;
+ private final String publicKey;
+ private final String uuid;
+ private final ProjectAttributes projectAttributes;
+ private final ReportAttributes reportAttributes;
+ private final FileAttributes fileAttributes;
+ private final List children;
+
+ private ReportComponent(Builder builder) {
+ this.type = builder.type;
+ this.status = builder.status;
+ this.key = builder.key;
+ this.publicKey = builder.publicKey;
+ this.name = builder.name == null ? String.valueOf(builder.key) : builder.name;
+ this.shortName = builder.shortName == null ? this.name : builder.shortName;
+ this.description = builder.description;
+ this.uuid = builder.uuid;
+ this.projectAttributes = Optional.ofNullable(builder.projectVersion)
+ .map(v -> new ProjectAttributes(v, builder.buildString, builder.scmRevisionId))
+ .orElse(null);
+ this.reportAttributes = ReportAttributes.newBuilder(builder.ref)
+ .build();
+ this.fileAttributes = builder.fileAttributes == null ? DEFAULT_FILE_ATTRIBUTES : builder.fileAttributes;
+ this.children = ImmutableList.copyOf(builder.children);
+ }
+
+ @Override
+ public Type getType() {
+ return type;
+ }
+
+ @Override
+ public Status getStatus() {
+ return status;
+ }
+
+ @Override
+ public String getUuid() {
+ if (uuid == null) {
+ throw new UnsupportedOperationException(String.format("Component uuid of ref '%d' has not be fed yet", this.reportAttributes.getRef()));
+ }
+ return uuid;
+ }
+
+ @Override
+ public String getDbKey() {
+ if (key == null) {
+ throw new UnsupportedOperationException(String.format("Component key of ref '%d' has not be fed yet", this.reportAttributes.getRef()));
+ }
+ return key;
+ }
+
+ @Override
+ public String getKey() {
+ if (publicKey == null) {
+ throw new UnsupportedOperationException(String.format("Component key of ref '%d' has not be fed yet", this.reportAttributes.getRef()));
+ }
+ return publicKey;
+ }
+
+ @Override
+ public String getName() {
+ return this.name;
+ }
+
+ @Override
+ public String getShortName() {
+ return this.shortName;
+ }
+
+ @Override
+ @CheckForNull
+ public String getDescription() {
+ return this.description;
+ }
+
+ @Override
+ public List getChildren() {
+ return children;
+ }
+
+ @Override
+ public ProjectAttributes getProjectAttributes() {
+ checkState(this.type == Type.PROJECT);
+ return this.projectAttributes;
+ }
+
+ @Override
+ public ReportAttributes getReportAttributes() {
+ return this.reportAttributes;
+ }
+
+ @Override
+ public FileAttributes getFileAttributes() {
+ checkState(this.type == Type.FILE, "Only component of type FILE can have a FileAttributes object");
+ return this.fileAttributes;
+ }
+
+ @Override
+ public ProjectViewAttributes getProjectViewAttributes() {
+ throw new IllegalStateException("Only component of type PROJECT_VIEW can have a ProjectViewAttributes object");
+ }
+
+ @Override
+ public SubViewAttributes getSubViewAttributes() {
+ throw new IllegalStateException("Only component of type SUBVIEW have a SubViewAttributes object");
+ }
+
+ @Override
+ public ViewAttributes getViewAttributes() {
+ throw new IllegalStateException("Only component of type VIEW have a ViewAttributes object");
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ReportComponent that = (ReportComponent) o;
+ return uuid.equals(that.uuid);
+ }
+
+ @Override
+ public int hashCode() {
+ return uuid.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "ReportComponent{" +
+ "ref=" + this.reportAttributes.getRef() +
+ ", key='" + key + '\'' +
+ ", type=" + type +
+ '}';
+ }
+
+ public static Builder builder(Type type, int ref) {
+ String key = "key_" + ref;
+ return new Builder(type, ref).setKey(key).setPublicKey(key).setUuid("uuid_" + ref).setName("name_" + ref);
+ }
+
+ public static final class Builder {
+ private final Type type;
+ private final int ref;
+ private Status status;
+ private String uuid;
+ private String key;
+ private String publicKey;
+ private String name;
+ private String shortName;
+ private String projectVersion;
+ private String buildString;
+ private String scmRevisionId;
+ private String description;
+ private FileAttributes fileAttributes;
+ private final List children = new ArrayList<>();
+
+ private Builder(Type type, int ref) {
+ checkArgument(type.isReportType(), "Component type must be a report type");
+ this.type = type;
+ this.ref = ref;
+ if (type == Type.PROJECT) {
+ this.projectVersion = "toBeDefined";
+ }
+ }
+
+ public Builder setStatus(Status s) {
+ this.status = requireNonNull(s);
+ return this;
+ }
+
+ public Builder setUuid(String s) {
+ this.uuid = requireNonNull(s);
+ return this;
+ }
+
+ public Builder setName(@Nullable String s) {
+ this.name = s;
+ return this;
+ }
+
+ public Builder setShortName(@Nullable String s) {
+ this.shortName = s;
+ return this;
+ }
+
+ public Builder setKey(String s) {
+ this.key = requireNonNull(s);
+ return this;
+ }
+
+ public Builder setPublicKey(String publicKey) {
+ this.publicKey = requireNonNull(publicKey);
+ return this;
+ }
+
+ public Builder setProjectVersion(@Nullable String s) {
+ checkProjectVersion(s);
+ this.projectVersion = s;
+ return this;
+ }
+
+ public Builder setBuildString(@Nullable String buildString) {
+ checkBuildString(buildString);
+ this.buildString = buildString;
+ return this;
+ }
+
+ public Builder setScmRevisionId(@Nullable String scmRevisionId) {
+ this.scmRevisionId = scmRevisionId;
+ return this;
+ }
+
+ public Builder setFileAttributes(FileAttributes fileAttributes) {
+ checkState(type == Type.FILE, "Only Component of type File can have File attributes");
+ this.fileAttributes = fileAttributes;
+ return this;
+ }
+
+ public Builder setDescription(@Nullable String description) {
+ this.description = description;
+ return this;
+ }
+
+ public Builder addChildren(Component... c) {
+ for (Component component : c) {
+ checkArgument(component.getType().isReportType());
+ }
+ this.children.addAll(asList(c));
+ return this;
+ }
+
+ public ReportComponent build() {
+ checkProjectVersion(this.projectVersion);
+ checkBuildString(this.buildString);
+ return new ReportComponent(this);
+ }
+
+ private void checkProjectVersion(@Nullable String s) {
+ checkArgument(type != Type.PROJECT ^ s != null, "Project version must and can only be set on Project");
+ }
+
+ private void checkBuildString(@Nullable String s) {
+ checkArgument(type == Type.PROJECT || s == null, "BuildString can only be set on Project");
+ }
+ }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/TreeComponentProvider.java b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/TreeComponentProvider.java
new file mode 100644
index 00000000000..10ebd61aeb7
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/TreeComponentProvider.java
@@ -0,0 +1,64 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ce.task.projectanalysis.component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkState;
+
+public final class TreeComponentProvider extends AbstractComponentProvider {
+ private final Component root;
+ private final Map componentsByRef = new HashMap<>();
+
+ public TreeComponentProvider(Component root) {
+ this.root = root;
+ ensureInitialized();
+ }
+
+ private static String getRef(Component component) {
+ return component.getType().isReportType() ? String.valueOf(component.getReportAttributes().getRef()) : component.getDbKey();
+ }
+
+ @Override
+ protected void ensureInitializedImpl() {
+ new DepthTraversalTypeAwareCrawler(
+ new TypeAwareVisitorAdapter(CrawlerDepthLimit.LEAVES, ComponentVisitor.Order.PRE_ORDER) {
+ @Override
+ public void visitAny(Component component) {
+ String ref = getRef(component);
+ checkState(!componentsByRef.containsKey(ref), "Tree contains more than one component with ref " + ref);
+ componentsByRef.put(ref, component);
+ }
+ }).visit(root);
+ }
+
+ @Override
+ protected void resetImpl() {
+ // we can not reset
+ }
+
+ @Override
+ protected Component getByRefImpl(int componentRef) {
+ Component component = componentsByRef.get(String.valueOf(componentRef));
+ checkState(component != null, "Can not find Component for ref " + componentRef);
+ return component;
+ }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/TreeRootHolderComponentProvider.java b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/TreeRootHolderComponentProvider.java
new file mode 100644
index 00000000000..da7eae0c878
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/TreeRootHolderComponentProvider.java
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ce.task.projectanalysis.component;
+
+public final class TreeRootHolderComponentProvider extends AbstractComponentProvider {
+ private final TreeRootHolder treeRootHolder;
+ private TreeComponentProvider delegate;
+
+ public TreeRootHolderComponentProvider(TreeRootHolder treeRootHolder) {
+ this.treeRootHolder = treeRootHolder;
+ }
+
+ @Override
+ protected void ensureInitializedImpl() {
+ if (this.delegate == null) {
+ this.delegate = new TreeComponentProvider(treeRootHolder.getRoot());
+ this.delegate.ensureInitialized();
+ }
+ }
+
+ @Override
+ protected void resetImpl() {
+ this.delegate = null;
+ }
+
+ @Override
+ protected Component getByRefImpl(int componentRef) {
+ return delegate.getByRef(componentRef);
+ }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/TreeRootHolderRule.java b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/TreeRootHolderRule.java
new file mode 100644
index 00000000000..bfdaa2fff06
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/TreeRootHolderRule.java
@@ -0,0 +1,77 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ce.task.projectanalysis.component;
+
+import java.util.Optional;
+import org.junit.rules.ExternalResource;
+
+public class TreeRootHolderRule extends ExternalResource implements TreeRootHolder {
+ protected TreeRootHolderImpl delegate = new TreeRootHolderImpl();
+
+ @Override
+ protected void after() {
+ this.delegate = null;
+ }
+
+ public TreeRootHolderRule setRoot(Component root) {
+ return setRoots(root, root);
+ }
+
+ public TreeRootHolderRule setRoots(Component root, Component reportRoot) {
+ delegate = new TreeRootHolderImpl();
+ delegate.setRoots(root, reportRoot);
+ return this;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return delegate.isEmpty();
+ }
+
+ @Override
+ public Component getRoot() {
+ return delegate.getRoot();
+ }
+
+ @Override
+ public Component getReportTreeRoot() {
+ return delegate.getReportTreeRoot();
+ }
+
+ @Override
+ public Component getComponentByRef(int ref) {
+ return delegate.getComponentByRef(ref);
+ }
+
+ @Override
+ public Optional getOptionalComponentByRef(int ref) {
+ return delegate.getOptionalComponentByRef(ref);
+ }
+
+ @Override public Component getReportTreeComponentByRef(int ref) {
+ return delegate.getReportTreeComponentByRef(ref);
+ }
+
+ @Override
+ public int getSize() {
+ return delegate.getSize();
+ }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/ViewsComponent.java b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/ViewsComponent.java
new file mode 100644
index 00000000000..a8f5afb35dd
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/component/ViewsComponent.java
@@ -0,0 +1,253 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ce.task.projectanalysis.component;
+
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Arrays.asList;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Implementation of {@link Component} to unit test views components.
+ */
+public class ViewsComponent implements Component {
+ private final Type type;
+ private final String key;
+ private final String uuid;
+ private final String name;
+ private final String description;
+ private final List children;
+ private final ProjectViewAttributes projectViewAttributes;
+ private final SubViewAttributes subViewAttributes;
+ private final ViewAttributes viewAttributes;
+
+ private ViewsComponent(Type type, String key, @Nullable String uuid, @Nullable String name, @Nullable String description,
+ List children, @Nullable ProjectViewAttributes projectViewAttributes, @Nullable SubViewAttributes subViewAttributes,
+ @Nullable ViewAttributes viewAttributes) {
+ checkArgument(type.isViewsType(), "Component type must be a Views type");
+ this.type = type;
+ this.key = requireNonNull(key);
+ this.uuid = uuid;
+ this.name = name;
+ this.description = description;
+ this.children = ImmutableList.copyOf(children);
+ this.projectViewAttributes = projectViewAttributes;
+ this.subViewAttributes = subViewAttributes;
+ this.viewAttributes = viewAttributes;
+ }
+
+ public static Builder builder(Type type, String key) {
+ return new Builder(type, key);
+ }
+
+ public static Builder builder(Type type, int key) {
+ return new Builder(type, String.valueOf(key));
+ }
+
+ public static final class Builder {
+ private final Type type;
+ private String key;
+ private String uuid;
+ private String name;
+ private String description;
+ private List children = new ArrayList<>();
+ private ProjectViewAttributes projectViewAttributes;
+ private SubViewAttributes subViewAttributes;
+ private ViewAttributes viewAttributes;
+
+ private Builder(Type type, String key) {
+ this.type = type;
+ this.key = key;
+ }
+
+ public Builder setUuid(@Nullable String uuid) {
+ this.uuid = uuid;
+ return this;
+ }
+
+ public Builder setKey(String key) {
+ this.key = key;
+ return this;
+ }
+
+ public Builder setName(@Nullable String name) {
+ this.name = name;
+ return this;
+ }
+
+ public Builder setDescription(String description) {
+ this.description = description;
+ return this;
+ }
+
+ public Builder setChildren(List children) {
+ this.children = children;
+ return this;
+ }
+
+ public Builder setProjectViewAttributes(@Nullable ProjectViewAttributes projectViewAttributes) {
+ this.projectViewAttributes = projectViewAttributes;
+ return this;
+ }
+
+ public Builder setSubViewAttributes(@Nullable SubViewAttributes subViewAttributes) {
+ this.subViewAttributes = subViewAttributes;
+ return this;
+ }
+
+ public Builder setViewAttributes(@Nullable ViewAttributes viewAttributes) {
+ this.viewAttributes = viewAttributes;
+ return this;
+ }
+
+ public Builder addChildren(Component... c) {
+ for (Component viewsComponent : c) {
+ checkArgument(viewsComponent.getType().isViewsType());
+ }
+ this.children.addAll(asList(c));
+ return this;
+ }
+
+ public ViewsComponent build() {
+ return new ViewsComponent(type, key, uuid, name, description, children, projectViewAttributes, subViewAttributes, viewAttributes);
+ }
+ }
+
+ @Override
+ public Type getType() {
+ return type;
+ }
+
+ @Override
+ public Status getStatus() {
+ return Status.UNAVAILABLE;
+ }
+
+ @Override
+ public String getUuid() {
+ return uuid;
+ }
+
+ @Override
+ public String getDbKey() {
+ return key;
+ }
+
+ /**
+ * Views has no branch feature, the public key is the same as the key
+ */
+ @Override
+ public String getKey() {
+ return getDbKey();
+ }
+
+ @Override
+ public String getName() {
+ checkState(this.name != null, "No name has been set");
+ return this.name;
+ }
+
+ @Override
+ public String getShortName() {
+ return getName();
+ }
+
+ @Override
+ @CheckForNull
+ public String getDescription() {
+ return this.description;
+ }
+
+ @Override
+ public List getChildren() {
+ return children;
+ }
+
+ @Override
+ public ProjectAttributes getProjectAttributes() {
+ throw new IllegalStateException("A component of type " + type + " does not have project attributes");
+ }
+
+ @Override
+ public ReportAttributes getReportAttributes() {
+ throw new IllegalStateException("A component of type " + type + " does not have report attributes");
+ }
+
+ @Override
+ public FileAttributes getFileAttributes() {
+ throw new IllegalStateException("A component of type " + type + " does not have file attributes");
+ }
+
+ @Override
+ public ProjectViewAttributes getProjectViewAttributes() {
+ checkState(this.type != Type.PROJECT_VIEW || this.projectViewAttributes != null, "A ProjectViewAttribute object should have been set");
+ return this.projectViewAttributes;
+ }
+
+ @Override
+ public SubViewAttributes getSubViewAttributes() {
+ checkState(this.type != Type.SUBVIEW || this.subViewAttributes != null, "A SubViewAttributes object should have been set");
+ return this.subViewAttributes;
+ }
+
+ @Override
+ public ViewAttributes getViewAttributes() {
+ checkState(this.type != Type.VIEW || this.viewAttributes != null, "A ViewAttributes object should have been set");
+ return viewAttributes;
+ }
+
+ @Override
+ public String toString() {
+ return "ViewsComponent{" +
+ "type=" + type +
+ ", key='" + key + '\'' +
+ ", uuid='" + uuid + '\'' +
+ ", name='" + name + '\'' +
+ ", children=" + children +
+ ", projectViewAttributes=" + projectViewAttributes +
+ ", subViewAttributes=" + subViewAttributes +
+ ", viewAttributes=" + viewAttributes +
+ '}';
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ViewsComponent that = (ViewsComponent) o;
+ return key.equals(that.key);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(key);
+ }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/measure/MeasureAssert.java b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/measure/MeasureAssert.java
new file mode 100644
index 00000000000..7b8d2eaa2bc
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/measure/MeasureAssert.java
@@ -0,0 +1,255 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ce.task.projectanalysis.measure;
+
+import java.util.Objects;
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.assertj.core.api.AbstractAssert;
+import org.assertj.core.data.Offset;
+
+import static java.lang.Math.abs;
+
+public class MeasureAssert extends AbstractAssert {
+
+ protected MeasureAssert(@Nullable Measure actual) {
+ super(actual, MeasureAssert.class);
+ }
+
+ public static MeasureAssert assertThat(Measure actual) {
+ return new MeasureAssert(actual);
+ }
+
+ public static MeasureAssert assertThat(@Nullable Optional actual) {
+ return new MeasureAssert(actual == null ? null : actual.orElse(null));
+ }
+
+ public MeasureAssert hasValueType(Measure.ValueType expected) {
+ isNotNull();
+
+ if (actual.getValueType() != expected) {
+ failWithMessage("Expected ValueType of Measure to be <%s> but was <%s>", expected, actual.getValueType());
+ }
+
+ return this;
+ }
+
+ public MeasureAssert hasValue(int expected) {
+ isNotNull();
+
+ if (actual.getValueType() != Measure.ValueType.INT) {
+ failWithMessage(
+ "Expected Measure to have an int value and therefore its ValueType to be <%s> but was <%s>",
+ Measure.ValueType.INT, actual.getValueType());
+ }
+
+ if (actual.getIntValue() != expected) {
+ failWithMessage("Expected value of Measure to be <%s> but was <%s>", expected, actual.getIntValue());
+ }
+
+ return this;
+ }
+
+ public MeasureAssert hasValue(long expected) {
+ isNotNull();
+
+ if (actual.getValueType() != Measure.ValueType.LONG) {
+ failWithMessage(
+ "Expected Measure to have a long value and therefore its ValueType to be <%s> but was <%s>",
+ Measure.ValueType.LONG, actual.getValueType());
+ }
+
+ if (actual.getLongValue() != expected) {
+ failWithMessage("Expected value of Measure to be <%s> but was <%s>", expected, actual.getLongValue());
+ }
+
+ return this;
+ }
+
+ public MeasureAssert hasValue(double expected) {
+ isNotNull();
+
+ if (actual.getValueType() != Measure.ValueType.DOUBLE) {
+ failWithMessage(
+ "Expected Measure to have a double value and therefore its ValueType to be <%s> but was <%s>",
+ Measure.ValueType.DOUBLE, actual.getValueType());
+ }
+
+ if (actual.getDoubleValue() != expected) {
+ failWithMessage("Expected value of Measure to be <%s> but was <%s>", expected, actual.getDoubleValue());
+ }
+
+ return this;
+ }
+
+ public MeasureAssert hasValue(boolean expected) {
+ isNotNull();
+
+ if (actual.getValueType() != Measure.ValueType.BOOLEAN) {
+ failWithMessage(
+ "Expected Measure to have a boolean value and therefore its ValueType to be <%s> but was <%s>",
+ Measure.ValueType.DOUBLE, actual.getValueType());
+ }
+
+ if (actual.getBooleanValue() != expected) {
+ failWithMessage("Expected value of Measure to be <%s> but was <%s>", expected, actual.getBooleanValue());
+ }
+
+ return this;
+ }
+
+ public MeasureAssert hasValue(String expected) {
+ isNotNull();
+
+ if (actual.getValueType() != Measure.ValueType.STRING) {
+ failWithMessage(
+ "Expected Measure to have a String value and therefore its ValueType to be <%s> but was <%s>",
+ Measure.ValueType.DOUBLE, actual.getValueType());
+ }
+
+ if (!Objects.equals(actual.getStringValue(), expected)) {
+ failWithMessage("Expected value of Measure to be <%s> but was <%s>", expected, actual.getStringValue());
+ }
+
+ return this;
+ }
+
+ public MeasureAssert hasValue(Measure.Level expected) {
+ isNotNull();
+
+ if (actual.getValueType() != Measure.ValueType.LEVEL) {
+ failWithMessage(
+ "Expected Measure to have a Level value and therefore its ValueType to be <%s> but was <%s>",
+ Measure.ValueType.DOUBLE, actual.getValueType());
+ }
+
+ if (actual.getLevelValue() != expected) {
+ failWithMessage("Expected value of Measure to be <%s> but was <%s>", expected, actual.getLevelValue());
+ }
+
+ return this;
+ }
+
+ public MeasureAssert hasNoValue() {
+ isNotNull();
+
+ if (actual.getValueType() != Measure.ValueType.NO_VALUE) {
+ failWithMessage(
+ "Expected Measure to have no value and therefore its ValueType to be <%s> but was <%s>",
+ Measure.ValueType.DOUBLE, actual.getValueType());
+ }
+
+ return this;
+ }
+
+ public MeasureAssert hasData(String expected) {
+ isNotNull();
+
+ if (!Objects.equals(actual.getData(), expected)) {
+ failWithMessage("Expected data of Measure to be <%s> but was <%s>", expected, actual.getData());
+ }
+
+ return this;
+ }
+
+ public MeasureAssert hasNoData() {
+ isNotNull();
+
+ if (actual.getData() == null) {
+ failWithMessage("Expected Measure to have no data but was <%s>", actual.getData());
+ }
+
+ return this;
+ }
+
+ public MeasureAssert hasQualityGateLevel(Measure.Level expected) {
+ isNotNull();
+ hasQualityGateStatus();
+
+ if (actual.getQualityGateStatus().getStatus() != expected) {
+ failWithMessage("Expected Level of QualityGateStatus of Measure to be <%s> but was <%s>", expected, actual.getQualityGateStatus().getStatus());
+ }
+
+ return this;
+ }
+
+ public MeasureAssert hasQualityGateText(String expected) {
+ isNotNull();
+ hasQualityGateStatus();
+
+ if (!Objects.equals(actual.getQualityGateStatus().getText(), expected)) {
+ failWithMessage("Expected text of QualityGateStatus of Measure to be \n<%s>\n but was \n<%s>", expected, actual.getQualityGateStatus().getText());
+ }
+
+ return this;
+ }
+
+ private void hasQualityGateStatus() {
+ if (!actual.hasQualityGateStatus()) {
+ failWithMessage("Expected Measure to have a QualityGateStatus but it did not");
+ }
+ }
+
+ public MeasureAssert hasVariation(double expected) {
+ isNotNull();
+ hasVariation();
+
+ if (!actual.hasVariation()) {
+ failWithMessage("Expected Measure to have a variation but it did not");
+ }
+
+ double variation = actual.getVariation();
+ if (variation != expected) {
+ failWithMessage("Expected variation of Measure to be <%s> but was <%s>", expected, variation);
+ }
+
+ return this;
+ }
+
+ public MeasureAssert hasVariation(double expected, Offset offset) {
+ isNotNull();
+ hasVariation();
+
+ if (!actual.hasVariation()) {
+ failWithMessage("Expected Measure to have a variation but it did not");
+ }
+
+ double variation = actual.getVariation();
+ if (abs(expected - variation) > offset.value) {
+ failWithMessage(
+ "Expected variation of Measure to be close to <%s> by less than <%s> but was <%s>",
+ expected, offset.value, variation);
+ }
+
+ return this;
+ }
+
+ private void hasVariation() {
+ if (!actual.hasVariation()) {
+ failWithMessage("Expected Measure to have a variation but it did not");
+ }
+ }
+
+ public void isAbsent() {
+ if (actual != null) {
+ failWithMessage("Expected measure to be absent");
+ }
+ }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/measure/MeasureRepoEntry.java b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/measure/MeasureRepoEntry.java
new file mode 100644
index 00000000000..2cf7fc130c6
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/measure/MeasureRepoEntry.java
@@ -0,0 +1,157 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ce.task.projectanalysis.measure;
+
+import com.google.common.base.Function;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.SetMultimap;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Map;
+import java.util.Objects;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.sonar.ce.task.projectanalysis.component.Component;
+
+/**
+ * This class represents a metric key and an associated measure.
+ * It can be used to easily compare the content of the SetMultimap returned by {@link MeasureRepository#getRawMeasures(Component)}
+ * or {@link MeasureRepositoryRule#getAddedRawMeasures(int)}.
+ *
+ * This class is also highly useful to accurately make sure of the SetMultimap content since this
+ * object implements a deep equals of Measure objects (see {@link #deepEquals(Measure, Measure)}), when
+ * {@link Measure#equals(Object)} only care about the ruleId and characteristicId.
+ *
+ *
+ * In order to explore the content of the SetMultimap, use {@link #toEntries(SetMultimap)} to convert it
+ * to an Iterable of {@link MeasureRepoEntry} and then take benefit of AssertJ API, eg.:
+ *
+ * assertThat(MeasureRepoEntry.toEntries(measureRepository.getAddedRawMeasures(componentRef))).containsOnly(
+ * MeasureRepoEntry.entryOf(DEVELOPMENT_COST_KEY, newMeasureBuilder().create(Long.toString(expectedDevCost))),
+ * MeasureRepoEntry.entryOf(SQALE_DEBT_RATIO_KEY, newMeasureBuilder().create(expectedDebtRatio))
+ * );
+ *
+ *
+ */
+public final class MeasureRepoEntry {
+ private final String metricKey;
+ private final Measure measure;
+
+ public MeasureRepoEntry(String metricKey, Measure measure) {
+ this.metricKey = metricKey;
+ this.measure = measure;
+ }
+
+ public static Function, MeasureRepoEntry> toMeasureRepoEntry() {
+ return EntryToMeasureRepoEntry.INSTANCE;
+ }
+
+ public static Iterable toEntries(SetMultimap data) {
+ return FluentIterable.from(data.entries()).transform(toMeasureRepoEntry()).toList();
+ }
+
+ public static MeasureRepoEntry entryOf(String metricKey, Measure measure) {
+ return new MeasureRepoEntry(metricKey, measure);
+ }
+
+ public static boolean deepEquals(Measure measure, Measure measure1) {
+ return measure.getValueType() == measure1.getValueType()
+ && equalsByValue(measure, measure1)
+ && equalsByVariation(measure, measure1)
+ && equalsByQualityGateStatus(measure, measure1)
+ && Objects.equals(measure.getData(), measure1.getData());
+ }
+
+ private static boolean equalsByValue(Measure measure, Measure measure1) {
+ switch (measure.getValueType()) {
+ case BOOLEAN:
+ return measure.getBooleanValue() == measure1.getBooleanValue();
+ case INT:
+ return measure.getIntValue() == measure1.getIntValue();
+ case LONG:
+ return measure.getLongValue() == measure1.getLongValue();
+ case DOUBLE:
+ return Double.compare(measure.getDoubleValue(), measure1.getDoubleValue()) == 0;
+ case STRING:
+ return measure.getStringValue().equals(measure1.getStringValue());
+ case LEVEL:
+ return measure.getLevelValue() == measure1.getLevelValue();
+ case NO_VALUE:
+ return true;
+ default:
+ throw new IllegalArgumentException("Unsupported ValueType " + measure.getValueType());
+ }
+ }
+
+ private static boolean equalsByVariation(Measure measure, Measure measure1) {
+ return measure.hasVariation() == measure1.hasVariation() && (!measure.hasVariation()
+ || Double.compare(scale(measure.getVariation()), scale(measure1.getVariation())) == 0);
+ }
+
+ private static final int DOUBLE_PRECISION = 1;
+
+ private static double scale(double value) {
+ BigDecimal bd = BigDecimal.valueOf(value);
+ return bd.setScale(DOUBLE_PRECISION, RoundingMode.HALF_UP).doubleValue();
+ }
+
+ private static boolean equalsByQualityGateStatus(Measure measure, Measure measure1) {
+ if (measure.hasQualityGateStatus() != measure1.hasQualityGateStatus()) {
+ return false;
+ }
+ if (!measure.hasQualityGateStatus()) {
+ return true;
+ }
+ return Objects.equals(measure.getQualityGateStatus(), measure1.getQualityGateStatus());
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ MeasureRepoEntry that = (MeasureRepoEntry) o;
+ return Objects.equals(metricKey, that.metricKey) &&
+ deepEquals(measure, that.measure);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(metricKey, measure);
+ }
+
+ @Override
+ public String toString() {
+ return "<" + metricKey + ", " + measure + '>';
+ }
+
+ private enum EntryToMeasureRepoEntry implements Function, MeasureRepoEntry> {
+ INSTANCE;
+
+ @Nullable
+ @Override
+ public MeasureRepoEntry apply(@Nonnull Map.Entry input) {
+ return new MeasureRepoEntry(input.getKey(), input.getValue());
+ }
+ }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/measure/MeasureRepositoryRule.java b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/measure/MeasureRepositoryRule.java
new file mode 100644
index 00000000000..2c579151024
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/measure/MeasureRepositoryRule.java
@@ -0,0 +1,316 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ce.task.projectanalysis.measure;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.SetMultimap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.junit.rules.ExternalResource;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.ComponentProvider;
+import org.sonar.ce.task.projectanalysis.component.NoComponentProvider;
+import org.sonar.ce.task.projectanalysis.component.TreeComponentProvider;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderComponentProvider;
+import org.sonar.ce.task.projectanalysis.metric.Metric;
+import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.FluentIterable.from;
+import static com.google.common.collect.Maps.filterKeys;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * An implementation of MeasureRepository as a JUnit rule which provides add methods for raw measures and extra add
+ * methods that takes component ref and metric keys thanks to the integration with various Component and Metric
+ * providers.
+ */
+public class MeasureRepositoryRule extends ExternalResource implements MeasureRepository {
+ private final ComponentProvider componentProvider;
+ @CheckForNull
+ private final MetricRepositoryRule metricRepositoryRule;
+ private final Map baseMeasures = new HashMap<>();
+ private final Map rawMeasures = new HashMap<>();
+ private final Map initialRawMeasures = new HashMap<>();
+ private final Predicate> isAddedMeasure = input -> !initialRawMeasures.containsKey(input.getKey())
+ || !MeasureRepoEntry.deepEquals(input.getValue(), initialRawMeasures.get(input.getKey()));
+
+ private MeasureRepositoryRule(ComponentProvider componentProvider, @Nullable MetricRepositoryRule metricRepositoryRule) {
+ this.componentProvider = componentProvider;
+ this.metricRepositoryRule = metricRepositoryRule;
+ }
+
+ @Override
+ protected void after() {
+ componentProvider.reset();
+ baseMeasures.clear();
+ rawMeasures.clear();
+ }
+
+ public static MeasureRepositoryRule create() {
+ return new MeasureRepositoryRule(NoComponentProvider.INSTANCE, null);
+ }
+
+ public static MeasureRepositoryRule create(TreeRootHolder treeRootHolder, MetricRepositoryRule metricRepositoryRule) {
+ return new MeasureRepositoryRule(new TreeRootHolderComponentProvider(treeRootHolder), requireNonNull(metricRepositoryRule));
+ }
+
+ public static MeasureRepositoryRule create(Component treeRoot, MetricRepositoryRule metricRepositoryRule) {
+ return new MeasureRepositoryRule(new TreeComponentProvider(treeRoot), requireNonNull(metricRepositoryRule));
+ }
+
+ public MeasureRepositoryRule addBaseMeasure(int componentRef, String metricKey, Measure measure) {
+ checkAndInitProvidersState();
+
+ InternalKey internalKey = new InternalKey(componentProvider.getByRef(componentRef), metricRepositoryRule.getByKey(metricKey));
+ checkState(!baseMeasures.containsKey(internalKey), format("Can not add a BaseMeasure twice for a Component (ref=%s) and Metric (key=%s)", componentRef, metricKey));
+
+ baseMeasures.put(internalKey, measure);
+
+ return this;
+ }
+
+ public SetMultimap getRawMeasures(int componentRef) {
+ return getRawMeasures(componentProvider.getByRef(componentRef));
+ }
+
+ /**
+ * Return measures that were added by the step (using {@link #add(Component, Metric, Measure)}).
+ * It does not contain the one added in the test by {@link #addRawMeasure(int, String, Measure)}
+ */
+ public SetMultimap getAddedRawMeasures(int componentRef) {
+ checkAndInitProvidersState();
+
+ return getAddedRawMeasures(componentProvider.getByRef(componentRef));
+ }
+
+ /**
+ * Return a measure that were added by the step (using {@link #add(Component, Metric, Measure)}).
+ * It does not contain the one added in the test by {@link #addRawMeasure(int, String, Measure)}
+ */
+ public Optional getAddedRawMeasure(Component component, String metricKey) {
+ return getAddedRawMeasure(component.getReportAttributes().getRef(), metricKey);
+ }
+
+ /**
+ * Return a measure that were added by the step (using {@link #add(Component, Metric, Measure)}).
+ * It does not contain the one added in the test by {@link #addRawMeasure(int, String, Measure)}
+ */
+ public Optional getAddedRawMeasure(int componentRef, String metricKey) {
+ checkAndInitProvidersState();
+
+ Set measures = getAddedRawMeasures(componentProvider.getByRef(componentRef)).get(metricKey);
+ if (measures.isEmpty()) {
+ return Optional.empty();
+ }
+ checkArgument(measures.size() == 1, String.format("There is more than one measure on metric '%s' for component '%s'", metricKey, componentRef));
+ return Optional.of(measures.iterator().next());
+ }
+
+ /**
+ * Return measures that were added by the step (using {@link #add(Component, Metric, Measure)}).
+ * It does not contain the one added in the test by {@link #addRawMeasure(int, String, Measure)}
+ */
+ public SetMultimap getAddedRawMeasures(Component component) {
+ checkAndInitProvidersState();
+
+ ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder();
+ for (Map.Entry entry : from(filterKeys(rawMeasures, hasComponentRef(component)).entrySet()).filter(isAddedMeasure)) {
+ builder.put(entry.getKey().getMetricKey(), entry.getValue());
+ }
+ return builder.build();
+ }
+
+ public MeasureRepositoryRule addRawMeasure(int componentRef, String metricKey, Measure measure) {
+ checkAndInitProvidersState();
+
+ InternalKey internalKey = new InternalKey(componentProvider.getByRef(componentRef), metricRepositoryRule.getByKey(metricKey));
+ checkState(!rawMeasures.containsKey(internalKey), format(
+ "A measure can only be set once for Component (ref=%s), Metric (key=%s)",
+ componentRef, metricKey));
+
+ rawMeasures.put(internalKey, measure);
+ initialRawMeasures.put(internalKey, measure);
+
+ return this;
+ }
+
+ @Override
+ public Optional getBaseMeasure(Component component, Metric metric) {
+ return Optional.ofNullable(baseMeasures.get(new InternalKey(component, metric)));
+ }
+
+ @Override
+ public Optional getRawMeasure(Component component, Metric metric) {
+ return Optional.ofNullable(rawMeasures.get(new InternalKey(component, metric)));
+ }
+
+ @Override
+ public Set getRawMeasures(Component component, Metric metric) {
+ return from(filterKeys(rawMeasures, hasComponentRef(component)).entrySet()).filter(new MatchMetric(metric)).transform(ToMeasure.INSTANCE).toSet();
+ }
+
+ @Override
+ public SetMultimap getRawMeasures(Component component) {
+ ImmutableSetMultimap.Builder builder = ImmutableSetMultimap.builder();
+ for (Map.Entry entry : filterKeys(rawMeasures, hasComponentRef(component)).entrySet()) {
+ builder.put(entry.getKey().getMetricKey(), entry.getValue());
+ }
+ return builder.build();
+ }
+
+ private HasComponentRefPredicate hasComponentRef(Component component) {
+ return new HasComponentRefPredicate(component);
+ }
+
+ @Override
+ public void add(Component component, Metric metric, Measure measure) {
+ String ref = getRef(component);
+ InternalKey internalKey = new InternalKey(ref, metric.getKey());
+ if (rawMeasures.containsKey(internalKey)) {
+ throw new UnsupportedOperationException(format(
+ "A measure can only be set once for Component (ref=%s), Metric (key=%s)",
+ ref, metric.getKey()));
+ }
+ rawMeasures.put(internalKey, measure);
+ }
+
+ @Override
+ public void update(Component component, Metric metric, Measure measure) {
+ String componentRef = getRef(component);
+ InternalKey internalKey = new InternalKey(componentRef, metric.getKey());
+ if (!rawMeasures.containsKey(internalKey)) {
+ throw new UnsupportedOperationException(format(
+ "A measure can only be updated if it has been added first for Component (ref=%s), Metric (key=%s)",
+ componentRef, metric.getKey()));
+ }
+ rawMeasures.put(internalKey, measure);
+ }
+
+ private void checkAndInitProvidersState() {
+ checkState(metricRepositoryRule != null, "Can not add a measure by metric key if MeasureRepositoryRule has not been created for a MetricRepository");
+ componentProvider.ensureInitialized();
+ }
+
+ public boolean isEmpty() {
+ return rawMeasures.isEmpty();
+ }
+
+ private static final class InternalKey {
+ private final String componentRef;
+ private final String metricKey;
+
+ public InternalKey(Component component, Metric metric) {
+ this(getRef(component), metric.getKey());
+ }
+
+ private InternalKey(String componentRef, String metricKey) {
+ this.componentRef = componentRef;
+ this.metricKey = metricKey;
+ }
+
+ public String getComponentRef() {
+ return componentRef;
+ }
+
+ public String getMetricKey() {
+ return metricKey;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ InternalKey that = (InternalKey) o;
+ return Objects.equals(componentRef, that.componentRef) &&
+ Objects.equals(metricKey, that.metricKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(componentRef, metricKey);
+ }
+
+ @Override
+ public String toString() {
+ return "InternalKey{" +
+ "component=" + componentRef +
+ ", metric='" + metricKey + '\'' +
+ '}';
+ }
+ }
+
+ private static class HasComponentRefPredicate implements Predicate {
+
+ private final String componentRef;
+
+ public HasComponentRefPredicate(Component component) {
+ this.componentRef = getRef(component);
+ }
+
+ @Override
+ public boolean apply(@Nonnull InternalKey input) {
+ return input.getComponentRef().equals(this.componentRef);
+ }
+ }
+
+ private static String getRef(Component component) {
+ return component.getType().isReportType() ? String.valueOf(component.getReportAttributes().getRef()) : component.getDbKey();
+ }
+
+ private static class MatchMetric implements Predicate> {
+ private final Metric metric;
+
+ public MatchMetric(Metric metric) {
+ this.metric = metric;
+ }
+
+ @Override
+ public boolean apply(@Nonnull Map.Entry input) {
+ return input.getKey().getMetricKey().equals(metric.getKey());
+ }
+ }
+
+ private enum ToMeasure implements Function, Measure> {
+ INSTANCE;
+
+ @Nullable
+ @Override
+ public Measure apply(@Nonnull Map.Entry input) {
+ return input.getValue();
+ }
+ }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/metric/MetricRepositoryRule.java b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/metric/MetricRepositoryRule.java
new file mode 100644
index 00000000000..7f781ff34b0
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/metric/MetricRepositoryRule.java
@@ -0,0 +1,116 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ce.task.projectanalysis.metric;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import org.junit.rules.ExternalResource;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+public class MetricRepositoryRule extends ExternalResource implements MetricRepository {
+ private final Map metricsByKey = new HashMap<>();
+ private final Map metricsById = new HashMap<>();
+
+ /**
+ * Convenience method to add a {@link Metric} to the repository created from a {@link org.sonar.api.measures.Metric},
+ * most of the time it will be a constant of the {@link org.sonar.api.measures.CoreMetrics} class.
+ *
+ * For the id of the created metric, this method uses the hashCode of the metric's key. If you want to specify
+ * the id of the create {@link Metric}, use {@link #add(int, org.sonar.api.measures.Metric)}
+ *
+ */
+ public MetricRepositoryRule add(org.sonar.api.measures.Metric> coreMetric) {
+ add(from(coreMetric));
+ return this;
+ }
+
+ /**
+ * Convenience method to add a {@link Metric} to the repository created from a {@link org.sonar.api.measures.Metric}
+ * and with the specified id, most of the time it will be a constant of the {@link org.sonar.api.measures.CoreMetrics}
+ * class.
+ */
+ public MetricRepositoryRule add(int id, org.sonar.api.measures.Metric> coreMetric) {
+ add(from(id, coreMetric));
+ return this;
+ }
+
+ private static Metric from(org.sonar.api.measures.Metric> coreMetric) {
+ return from(coreMetric.getKey().hashCode(), coreMetric);
+ }
+
+ private static Metric from(int id, org.sonar.api.measures.Metric> coreMetric) {
+ return new MetricImpl(
+ id, coreMetric.getKey(), coreMetric.getName(),
+ convert(coreMetric.getType()),
+ coreMetric.getDecimalScale(),
+ coreMetric.getBestValue(), coreMetric.isOptimizedBestValue());
+ }
+
+ private static Metric.MetricType convert(org.sonar.api.measures.Metric.ValueType coreMetricType) {
+ return Metric.MetricType.valueOf(coreMetricType.name());
+ }
+
+ public MetricRepositoryRule add(Metric metric) {
+ requireNonNull(metric.getKey(), "key can not be null");
+ requireNonNull(metric.getId(), "id can not be null");
+
+ checkState(!metricsByKey.containsKey(metric.getKey()), format("Repository already contains a metric for key %s", metric.getKey()));
+ checkState(!metricsById.containsKey((long) metric.getId()), format("Repository already contains a metric for id %s", metric.getId()));
+
+ metricsByKey.put(metric.getKey(), metric);
+ metricsById.put((long) metric.getId(), metric);
+
+ return this;
+ }
+
+ @Override
+ protected void after() {
+ this.metricsById.clear();
+ this.metricsById.clear();
+ }
+
+ @Override
+ public Metric getByKey(String key) {
+ Metric res = metricsByKey.get(key);
+ checkState(res != null, format("No Metric can be found for key %s", key));
+ return res;
+ }
+
+ @Override
+ public Metric getById(long id) {
+ Metric res = metricsById.get(id);
+ checkState(res != null, format("No Metric can be found for id %s", id));
+ return res;
+ }
+
+ @Override
+ public Optional getOptionalById(long id) {
+ return Optional.of(metricsById.get(id));
+ }
+
+ @Override
+ public Iterable getAll() {
+ return metricsByKey.values();
+ }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/step/BaseStepTest.java b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/step/BaseStepTest.java
new file mode 100644
index 00000000000..16aed8003df
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/testFixtures/java/org/sonar/ce/task/projectanalysis/step/BaseStepTest.java
@@ -0,0 +1,40 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.ce.task.projectanalysis.step;
+
+import org.junit.Test;
+import org.sonar.ce.task.step.ComputationStep;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Temporary solution to test metadata. Should be replaced by a medium test of
+ * all computation stack
+ */
+public abstract class BaseStepTest {
+
+ protected abstract ComputationStep step();
+
+ @Test
+ public void test_metadata() {
+ assertThat(step().toString()).isNotEmpty();
+ assertThat(step().getDescription()).isNotEmpty();
+ }
+}