From 6a5750d2bf9788cd405d4b94fb839013a25671c3 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Fri, 7 Aug 2015 14:03:19 +0200 Subject: [PATCH] SONAR-6730 Compute plugin measures in CE --- .../container/ComputeEngineContainerImpl.java | 26 +- .../measure/MeasureComputersHolder.java | 33 +++ .../measure/MeasureComputersHolderImpl.java | 53 ++++ .../measure/api/ComponentImpl.java | 126 +++++++++ .../measure/api/MeasureComputerImpl.java | 128 +++++++++ .../MeasureComputerImplementationContext.java | 192 +++++++++++++ .../api/MeasureComputerProviderContext.java | 89 +++++++ .../computation/measure/api/MeasureImpl.java | 86 ++++++ .../computation/measure/api/package-info.java | 24 ++ .../computation/step/ComputationSteps.java | 3 + .../step/ComputePluginMeasuresStep.java | 78 ++++++ .../step/FeedMeasureComputers.java | 174 ++++++++++++ .../MeasureComputersHolderImplTest.java | 73 +++++ .../measure/api/ComponentImplTest.java | 122 +++++++++ .../measure/api/MeasureComputerImplTest.java | 204 ++++++++++++++ ...sureComputerImplementationContextTest.java | 252 ++++++++++++++++++ .../MeasureComputerProviderContextTest.java | 153 +++++++++++ .../measure/api/MeasureImplTest.java | 118 ++++++++ .../step/ComputePluginMeasuresStepTest.java | 160 +++++++++++ .../step/FeedMeasureComputersTest.java | 216 +++++++++++++++ 20 files changed, 2298 insertions(+), 12 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolder.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolderImpl.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/ComponentImpl.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerImpl.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerImplementationContext.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContext.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureImpl.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/package-info.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputePluginMeasuresStep.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/step/FeedMeasureComputers.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureComputersHolderImplTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/ComponentImplTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerImplTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerImplementationContextTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContextTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureImplTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputePluginMeasuresStepTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/step/FeedMeasureComputersTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java index eeba9f111e8..0a0b20c2b20 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java @@ -76,6 +76,7 @@ import org.sonar.server.computation.issue.commonrule.LineCoverageRule; import org.sonar.server.computation.issue.commonrule.SkippedTestRule; import org.sonar.server.computation.issue.commonrule.TestErrorRule; import org.sonar.server.computation.language.LanguageRepositoryImpl; +import org.sonar.server.computation.measure.MeasureComputersHolderImpl; import org.sonar.server.computation.measure.MeasureRepositoryImpl; import org.sonar.server.computation.measure.newcoverage.NewCoverageMetricKeysModule; import org.sonar.server.computation.metric.MetricModule; @@ -176,11 +177,11 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co return Arrays.asList( new ComputationTempFolderProvider(), - ActivityManager.class, + ActivityManager.class, - MetricModule.class, + MetricModule.class, - // holders + // holders BatchReportDirectoryHolderImpl.class, TreeRootHolderImpl.class, PeriodsHolderImpl.class, @@ -188,10 +189,11 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co DebtModelHolderImpl.class, SqaleRatingSettings.class, ActiveRulesHolderImpl.class, + MeasureComputersHolderImpl.class, - BatchReportReaderImpl.class, + BatchReportReaderImpl.class, - // repositories + // repositories LanguageRepositoryImpl.class, MeasureRepositoryImpl.class, EventRepositoryImpl.class, @@ -200,10 +202,10 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co QualityGateServiceImpl.class, EvaluationResultTextConverterImpl.class, - // new coverage measures + // new coverage measures NewCoverageMetricKeysModule.class, - // issues + // issues RuleCacheLoader.class, RuleRepositoryImpl.class, ScmAccountToUserLoader.class, @@ -213,7 +215,7 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co IssueVisitors.class, IssueLifecycle.class, - // common rules + // common rules CommonRuleEngineImpl.class, BranchCoverageRule.class, LineCoverageRule.class, @@ -222,7 +224,7 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co TestErrorRule.class, SkippedTestRule.class, - // order is important: DebtAggregator then NewDebtAggregator (new debt requires debt) + // order is important: DebtAggregator then NewDebtAggregator (new debt requires debt) DebtCalculator.class, DebtAggregator.class, NewDebtCalculator.class, @@ -231,17 +233,17 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co RuleTagsCopier.class, IssueCounter.class, - UpdateConflictResolver.class, + UpdateConflictResolver.class, TrackerBaseInputFactory.class, TrackerRawInputFactory.class, Tracker.class, TrackerExecution.class, BaseIssuesLoader.class, - // views + // views ViewIndex.class, - // ComputationService + // ComputationService ComputationService.class); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolder.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolder.java new file mode 100644 index 00000000000..61a1ac39f33 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolder.java @@ -0,0 +1,33 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.measure; + +import org.sonar.api.ce.measure.MeasureComputer; + +public interface MeasureComputersHolder { + + /** + * Return the list of measure computers sorted by the way they should be executed + * + * @throws IllegalStateException if the measure computers haven't been initialized + */ + Iterable getMeasureComputers(); +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolderImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolderImpl.java new file mode 100644 index 00000000000..1bec032901a --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/MeasureComputersHolderImpl.java @@ -0,0 +1,53 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.measure; + +import com.google.common.base.Predicates; +import javax.annotation.CheckForNull; +import org.sonar.api.ce.measure.MeasureComputer; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.FluentIterable.from; +import static java.util.Objects.requireNonNull; + +public class MeasureComputersHolderImpl implements MeasureComputersHolder { + + @CheckForNull + private Iterable measureComputers; + + @Override + public Iterable getMeasureComputers() { + checkState(this.measureComputers != null, "Measure computers have not been initialized yet"); + return measureComputers; + } + + /** + * Initializes the measure computers in the holder. + * + * @throws NullPointerException if the specified List is {@code null} + * @throws IllegalStateException if the holder has already been initialized + */ + public void setMeasureComputers(Iterable measureComputers) { + requireNonNull(measureComputers, "Measure computers cannot be null"); + checkState(this.measureComputers == null, "Measure computers have already been initialized"); + this.measureComputers = from(measureComputers).filter(Predicates.notNull()).toList(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/ComponentImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/ComponentImpl.java new file mode 100644 index 00000000000..5578afdbcee --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/ComponentImpl.java @@ -0,0 +1,126 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.measure.api; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import org.sonar.api.ce.measure.Component; + +import static com.google.common.base.Preconditions.checkState; +import static java.util.Objects.requireNonNull; + +@Immutable +public class ComponentImpl implements Component { + + private final String key; + + private final Type type; + + @CheckForNull + private final FileAttributes fileAttributes; + + public ComponentImpl(String key, Type type, @Nullable FileAttributes fileAttributes) { + this.key = requireNonNull(key, "Key cannot be null"); + this.type = requireNonNull(type, "Type cannot be null"); + this.fileAttributes = checkFileAttributes(fileAttributes); + } + + @CheckForNull + private FileAttributes checkFileAttributes(@Nullable FileAttributes fileAttributes) { + if (fileAttributes == null && type == Type.FILE) { + throw new IllegalArgumentException("Component of type FILE must have a FileAttributes object"); + } else if (fileAttributes != null && type != Type.FILE) { + throw new IllegalArgumentException("Only component of type FILE have a FileAttributes object"); + } + return fileAttributes; + } + + @Override + public Type getType() { + return type; + } + + @Override + public FileAttributes getFileAttributes() { + checkState(this.type == Component.Type.FILE, "Only component of type FILE have a FileAttributes object"); + return fileAttributes; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ComponentImpl component = (ComponentImpl) o; + + return key.equals(component.key); + } + + @Override + public int hashCode() { + return key.hashCode(); + } + + @Override + public String toString() { + return "ComponentImpl{" + + "key=" + key + + ", type='" + type + '\'' + + ", fileAttributes=" + fileAttributes + + '}'; + } + + @Immutable + public static class FileAttributesImpl implements FileAttributes { + + private final boolean unitTest; + private final String languageKey; + + public FileAttributesImpl(@Nullable String languageKey, boolean unitTest) { + this.languageKey = languageKey; + this.unitTest = unitTest; + } + + @Override + public boolean isUnitTest() { + return unitTest; + } + + @Override + @CheckForNull + public String getLanguageKey() { + return languageKey; + } + + @Override + public String toString() { + return "FileAttributesImpl{" + + "languageKey='" + languageKey + '\'' + + ", unitTest=" + unitTest + + '}'; + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerImpl.java new file mode 100644 index 00000000000..02ac2759a52 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerImpl.java @@ -0,0 +1,128 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.measure.api; + +import com.google.common.collect.ImmutableSet; +import java.util.Set; +import org.sonar.api.ce.measure.MeasureComputer; + +import static com.google.common.base.Preconditions.checkArgument; + +public class MeasureComputerImpl implements MeasureComputer { + + private final Set inputMetricKeys; + private final Set outputMetrics; + private final Implementation implementation; + + public MeasureComputerImpl(MeasureComputerBuilderImpl builder) { + this.inputMetricKeys = ImmutableSet.copyOf(builder.inputMetricKeys); + this.outputMetrics = ImmutableSet.copyOf(builder.outputMetrics); + this.implementation = builder.measureComputerImplementation; + } + + @Override + public Set getInputMetrics() { + return inputMetricKeys; + } + + @Override + public Set getOutputMetrics() { + return outputMetrics; + } + + @Override + public Implementation getImplementation() { + return implementation; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + MeasureComputerImpl that = (MeasureComputerImpl) o; + + if (!inputMetricKeys.equals(that.inputMetricKeys)) { + return false; + } + return outputMetrics.equals(that.outputMetrics); + } + + @Override + public int hashCode() { + int result = inputMetricKeys.hashCode(); + result = 31 * result + outputMetrics.hashCode(); + return result; + } + + @Override + public String toString() { + return "MeasureComputerImpl{" + + "inputMetricKeys=" + inputMetricKeys + + ", outputMetrics=" + outputMetrics + + ", implementation=" + implementation + + '}'; + } + + public static class MeasureComputerBuilderImpl implements MeasureComputerBuilder { + + private String[] inputMetricKeys; + private String[] outputMetrics; + private Implementation measureComputerImplementation; + + @Override + public MeasureComputerBuilder setInputMetrics(String... inputMetrics) { + checkArgument(inputMetrics != null && inputMetrics.length > 0, "At least one input metric must be defined"); + this.inputMetricKeys = inputMetrics; + return this; + } + + @Override + public MeasureComputerBuilder setOutputMetrics(String... outputMetrics) { + checkArgument(outputMetrics != null && outputMetrics.length > 0, "At least one output metric must be defined"); + this.outputMetrics = outputMetrics; + return this; + } + + @Override + public MeasureComputerBuilder setImplementation(Implementation impl) { + checkImplementation(impl); + this.measureComputerImplementation = impl; + return this; + } + + @Override + public MeasureComputer build() { + checkArgument(this.inputMetricKeys != null, "At least one input metric must be defined"); + checkArgument(this.outputMetrics != null, "At least one output metric must be defined"); + checkImplementation(this.measureComputerImplementation); + return new MeasureComputerImpl(this); + } + + private static void checkImplementation(Implementation impl) { + checkArgument(impl != null, "The implementation is missing"); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerImplementationContext.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerImplementationContext.java new file mode 100644 index 00000000000..b710678e68e --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerImplementationContext.java @@ -0,0 +1,192 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.measure.api; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Predicates; +import com.google.common.collect.FluentIterable; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.sonar.api.ce.measure.Component; +import org.sonar.api.ce.measure.Measure; +import org.sonar.api.ce.measure.MeasureComputer; +import org.sonar.api.ce.measure.Settings; +import org.sonar.server.computation.component.ProjectSettingsRepository; +import org.sonar.server.computation.measure.MeasureRepository; +import org.sonar.server.computation.metric.Metric; +import org.sonar.server.computation.metric.MetricRepository; + +import static com.google.common.base.Preconditions.checkArgument; +import static org.sonar.server.computation.measure.Measure.newMeasureBuilder; + +public class MeasureComputerImplementationContext implements MeasureComputer.Implementation.Context { + + private final MeasureComputer measureComputer; + private final ProjectSettingsRepository settings; + private final MeasureRepository measureRepository; + private final MetricRepository metricRepository; + + private final org.sonar.server.computation.component.Component internalComponent; + private final Component component; + + private final Set allowedMetrics; + + public MeasureComputerImplementationContext(org.sonar.server.computation.component.Component component, MeasureComputer measureComputer, + ProjectSettingsRepository settings, MeasureRepository measureRepository, MetricRepository metricRepository) { + this.measureComputer = measureComputer; + this.settings = settings; + this.internalComponent = component; + this.measureRepository = measureRepository; + this.metricRepository = metricRepository; + this.component = newComponent(component); + this.allowedMetrics = allowedMetric(measureComputer); + } + + private Component newComponent(org.sonar.server.computation.component.Component component) { + return new ComponentImpl( + component.getKey(), + Component.Type.valueOf(component.getType().name()), + component.getType() == org.sonar.server.computation.component.Component.Type.FILE ? + new ComponentImpl.FileAttributesImpl(component.getFileAttributes().getLanguageKey(), component.getFileAttributes().isUnitTest()) : + null + ); + } + + private static Set allowedMetric(MeasureComputer measureComputer){ + Set allowedMetrics = new HashSet<>(); + allowedMetrics.addAll(measureComputer.getInputMetrics()); + allowedMetrics.addAll(measureComputer.getOutputMetrics()); + return allowedMetrics; + } + + @Override + public Component getComponent() { + return component; + } + + @Override + public Settings getSettings() { + return new Settings() { + @Override + @CheckForNull + public String getString(String key) { + return getComponentSettings().getString(key); + } + + @Override + public String[] getStringArray(String key) { + return getComponentSettings().getStringArray(key); + } + }; + } + + private org.sonar.api.config.Settings getComponentSettings(){ + return settings.getProjectSettings(internalComponent.getKey()); + } + + @Override + @CheckForNull + public Measure getMeasure(String metric) { + validateInputMetric(metric); + Optional measure = measureRepository.getRawMeasure(internalComponent, metricRepository.getByKey(metric)); + if (measure.isPresent()) { + return new MeasureImpl(measure.get()); + } + return null; + } + + @Override + public Iterable getChildrenMeasures(String metric) { + validateInputMetric(metric); + return FluentIterable.from(internalComponent.getChildren()) + .transform(new ComponentToMeasure(metricRepository.getByKey(metric))) + .transform(ToMeasureAPI.INSTANCE) + .filter(Predicates.notNull()); + } + + @Override + public void addMeasure(String metricKey, int value) { + Metric metric = metricRepository.getByKey(metricKey); + validateAddMeasure(metric); + measureRepository.add(internalComponent, metric, newMeasureBuilder().create(value)); + } + + @Override + public void addMeasure(String metricKey, double value) { + Metric metric = metricRepository.getByKey(metricKey); + validateAddMeasure(metric); + measureRepository.add(internalComponent, metric, newMeasureBuilder().create(value)); + } + + @Override + public void addMeasure(String metricKey, long value) { + Metric metric = metricRepository.getByKey(metricKey); + validateAddMeasure(metric); + measureRepository.add(internalComponent, metric, newMeasureBuilder().create(value)); + } + + @Override + public void addMeasure(String metricKey, String value) { + Metric metric = metricRepository.getByKey(metricKey); + validateAddMeasure(metric); + measureRepository.add(internalComponent, metric, newMeasureBuilder().create(value)); + } + + private void validateInputMetric(String metric) { + checkArgument(allowedMetrics.contains(metric), "Only metrics in %s can be used to load measures", measureComputer.getInputMetrics()); + } + + private void validateAddMeasure(Metric metric) { + checkArgument(measureComputer.getOutputMetrics().contains(metric.getKey()), "Only metrics in %s can be used to add measures", measureComputer.getOutputMetrics()); + if (measureRepository.getRawMeasure(internalComponent, metric).isPresent()) { + throw new UnsupportedOperationException(String.format("A measure on metric '%s' already exists on component '%s'", metric.getKey(), internalComponent.getKey())); + } + } + + private class ComponentToMeasure implements Function> { + + private final Metric metric; + + public ComponentToMeasure(Metric metric) { + this.metric = metric; + } + + @Nullable + @Override + public Optional apply(@Nonnull org.sonar.server.computation.component.Component input) { + return measureRepository.getRawMeasure(input, metric); + } + } + + private enum ToMeasureAPI implements Function, Measure> { + INSTANCE; + + @Nullable + @Override + public Measure apply(@Nonnull Optional input) { + return input.isPresent() ? new MeasureImpl(input.get()) : null; + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContext.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContext.java new file mode 100644 index 00000000000..27be1a2338d --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContext.java @@ -0,0 +1,89 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.measure.api; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.sonar.api.ce.measure.MeasureComputer; +import org.sonar.api.ce.measure.MeasureComputerProvider; + +public class MeasureComputerProviderContext implements MeasureComputerProvider.Context { + + private final List measureComputers = new ArrayList<>(); + private final Map computerByOutputMetrics = new HashMap<>(); + + @Override + public MeasureComputerProvider.Context add(MeasureComputer inputMeasureComputer) { + MeasureComputer measureComputer = validateMeasureComputer(inputMeasureComputer); + checkOutputMetricsNotAlreadyDefinedByAnotherComputer(measureComputer); + this.measureComputers.add(measureComputer); + for (String metric : measureComputer.getOutputMetrics()) { + computerByOutputMetrics.put(metric, measureComputer); + } + return this; + } + + private MeasureComputer validateMeasureComputer(MeasureComputer measureComputer) { + if (measureComputer instanceof MeasureComputerImpl) { + return measureComputer; + } + // If the computer has not been created by the builder, we recreate it to make sure it's valid + Set inputMetrics = measureComputer.getInputMetrics(); + Set outputMetrics = measureComputer.getOutputMetrics(); + return newMeasureComputerBuilder() + .setInputMetrics(inputMetrics.toArray(new String[inputMetrics.size()])) + .setOutputMetrics(outputMetrics.toArray(new String[outputMetrics.size()])) + .setImplementation(measureComputer.getImplementation()) + .build(); + } + + public List getMeasureComputers() { + return measureComputers; + } + + private void checkOutputMetricsNotAlreadyDefinedByAnotherComputer(MeasureComputer measureComputer) { + Set duplicated = ImmutableSet.copyOf(Sets.intersection(computerByOutputMetrics.keySet(), measureComputer.getOutputMetrics())); + if (!duplicated.isEmpty()) { + throw new UnsupportedOperationException(generateErrorMsg(duplicated)); + } + } + + private String generateErrorMsg(Set duplicated){ + StringBuilder errorMsg = new StringBuilder(); + for (String duplicationMetric : duplicated) { + MeasureComputer otherComputer = computerByOutputMetrics.get(duplicationMetric); + errorMsg.append(String.format( + "The output metric '%s' is already declared by another computer. This computer has these input metrics '%s' and these output metrics '%s'. ", + duplicationMetric, otherComputer.getInputMetrics(), otherComputer.getOutputMetrics())); + } + return errorMsg.toString(); + } + + @Override + public MeasureComputer.MeasureComputerBuilder newMeasureComputerBuilder() { + return new MeasureComputerImpl.MeasureComputerBuilderImpl(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureImpl.java new file mode 100644 index 00000000000..285a05ab68e --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/MeasureImpl.java @@ -0,0 +1,86 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.measure.api; + +import com.google.common.collect.ImmutableSet; +import java.util.Locale; +import java.util.Set; +import javax.annotation.concurrent.Immutable; +import org.sonar.api.ce.measure.Measure; + +import static com.google.common.base.Preconditions.checkState; +import static java.util.Objects.requireNonNull; +import static org.sonar.server.computation.measure.Measure.ValueType.DOUBLE; +import static org.sonar.server.computation.measure.Measure.ValueType.INT; +import static org.sonar.server.computation.measure.Measure.ValueType.LONG; +import static org.sonar.server.computation.measure.Measure.ValueType.STRING; + +@Immutable +public class MeasureImpl implements Measure { + + private static final Set ALLOWED_VALUE_TYPES = ImmutableSet.of(INT, LONG, DOUBLE, STRING); + + private final org.sonar.server.computation.measure.Measure measure; + + public MeasureImpl(org.sonar.server.computation.measure.Measure measure) { + this.measure = requireNonNull(measure, "Measure couldn't be null"); + checkState(ALLOWED_VALUE_TYPES.contains(measure.getValueType()), String.format("Only following types are allowed %s", ALLOWED_VALUE_TYPES)); + } + + @Override + public int getIntValue() { + checkValueType(INT); + return measure.getIntValue(); + } + + @Override + public long getLongValue() { + checkValueType(LONG); + return measure.getLongValue(); + } + + @Override + public double getDoubleValue() { + checkValueType(DOUBLE); + return measure.getDoubleValue(); + } + + @Override + public String getStringValue() { + checkValueType(STRING); + return measure.getStringValue(); + } + + private void checkValueType(org.sonar.server.computation.measure.Measure.ValueType expected) { + checkState(measure.getValueType() == expected, String.format( + "Value can not be converted to %s because current value type is a %s", + expected.toString().toLowerCase(Locale.US), + measure.getValueType() + )); + } + + @Override + public String toString() { + return "MeasureImpl{" + + "measure=" + measure + + '}'; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/package-info.java new file mode 100644 index 00000000000..c96a98a518c --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/measure/api/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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. + */ + +@ParametersAreNonnullByDefault +package org.sonar.server.computation.measure.api; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java index 22079297a05..ed0d26b0286 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java @@ -67,6 +67,9 @@ public class ComputationSteps { // SQALE measures depend on issues SqaleMeasuresStep.class, + FeedMeasureComputers.class, + ComputePluginMeasuresStep.class, + // Must be executed after computation of all measures FillMeasuresWithVariationsStep.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputePluginMeasuresStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputePluginMeasuresStep.java new file mode 100644 index 00000000000..f336a5970b7 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputePluginMeasuresStep.java @@ -0,0 +1,78 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.step; + +import org.sonar.api.ce.measure.MeasureComputer; +import org.sonar.server.computation.component.DepthTraversalTypeAwareVisitor; +import org.sonar.server.computation.component.ProjectSettingsRepository; +import org.sonar.server.computation.component.TreeRootHolder; +import org.sonar.server.computation.measure.MeasureComputersHolder; +import org.sonar.server.computation.measure.MeasureRepository; +import org.sonar.server.computation.measure.api.MeasureComputerImplementationContext; +import org.sonar.server.computation.metric.MetricRepository; + +import static org.sonar.server.computation.component.Component.Type.FILE; +import static org.sonar.server.computation.component.ComponentVisitor.Order.PRE_ORDER; + +public class ComputePluginMeasuresStep implements ComputationStep { + + private final TreeRootHolder treeRootHolder; + private final MetricRepository metricRepository; + private final MeasureRepository measureRepository; + private final ProjectSettingsRepository settings; + + private final MeasureComputersHolder measureComputersHolder; + + public ComputePluginMeasuresStep(TreeRootHolder treeRootHolder, MetricRepository metricRepository, MeasureRepository measureRepository, ProjectSettingsRepository settings, + MeasureComputersHolder measureComputersHolder) { + this.treeRootHolder = treeRootHolder; + this.metricRepository = metricRepository; + this.measureRepository = measureRepository; + this.settings = settings; + this.measureComputersHolder = measureComputersHolder; + } + + @Override + public void execute() { + new NewMetricDefinitionsVisitor().visit(treeRootHolder.getRoot()); + } + + private class NewMetricDefinitionsVisitor extends DepthTraversalTypeAwareVisitor { + + public NewMetricDefinitionsVisitor() { + super(FILE, PRE_ORDER); + } + + @Override + public void visitAny(org.sonar.server.computation.component.Component component) { + for (MeasureComputer computer : measureComputersHolder.getMeasureComputers()) { + MeasureComputerImplementationContext measureComputerContext = new MeasureComputerImplementationContext(component, computer, settings, measureRepository, metricRepository); + computer.getImplementation().compute(measureComputerContext); + } + } + } + + @Override + public String getDescription() { + return "Compute measures from plugin"; + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/FeedMeasureComputers.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/FeedMeasureComputers.java new file mode 100644 index 00000000000..36467e7fce1 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/FeedMeasureComputers.java @@ -0,0 +1,174 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.step; + +import com.google.common.base.Function; +import com.google.common.base.Predicates; +import com.google.common.collect.FluentIterable; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.sonar.api.ce.measure.MeasureComputer; +import org.sonar.api.ce.measure.MeasureComputerProvider; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Metric; +import org.sonar.api.measures.Metrics; +import org.sonar.api.utils.dag.DirectAcyclicGraph; +import org.sonar.server.computation.measure.MeasureComputersHolderImpl; +import org.sonar.server.computation.measure.api.MeasureComputerProviderContext; + +public class FeedMeasureComputers implements ComputationStep { + + private static final Set CORE_METRIC_KEYS = FluentIterable.from(CoreMetrics.getMetrics()).transform(MetricToKey.INSTANCE).toSet(); + + private final MeasureComputersHolderImpl measureComputersHolder; + private final Metrics[] metricsRepositories; + private final MeasureComputerProvider[] measureComputerProviders; + + public FeedMeasureComputers(MeasureComputersHolderImpl measureComputersHolder, Metrics[] metricsRepositories, MeasureComputerProvider[] measureComputerProviders) { + this.measureComputersHolder = measureComputersHolder; + this.measureComputerProviders = measureComputerProviders; + this.metricsRepositories = metricsRepositories; + } + + /** + * Constructor override used by Pico to instantiate the class when no plugin is defining metrics + */ + public FeedMeasureComputers(MeasureComputersHolderImpl measureComputersHolder, MeasureComputerProvider[] measureComputerProviders) { + this(measureComputersHolder, new Metrics[] {}, measureComputerProviders); + } + + /** + * Constructor override used by Pico to instantiate the class when no plugin is defining measure computers + */ + public FeedMeasureComputers(MeasureComputersHolderImpl measureComputersHolder, Metrics[] metricsRepositories) { + this(measureComputersHolder, metricsRepositories, new MeasureComputerProvider[] {}); + } + + /** + * Constructor override used by Pico to instantiate the class when no plugin is defining metrics neither measure computers + */ + public FeedMeasureComputers(MeasureComputersHolderImpl measureComputersHolder) { + this(measureComputersHolder, new Metrics[] {}, new MeasureComputerProvider[] {}); + } + + @Override + public void execute() { + MeasureComputerProviderContext context = new MeasureComputerProviderContext(); + for (MeasureComputerProvider provider : measureComputerProviders) { + provider.register(context); + } + validateInputMetrics(context.getMeasureComputers()); + measureComputersHolder.setMeasureComputers(sortComputers(context.getMeasureComputers())); + } + + private static Iterable sortComputers(List computers) { + Map computersByOutputMetric = new HashMap<>(); + Map computersByInputMetric = new HashMap<>(); + for (MeasureComputer computer : computers) { + for (String outputMetric : computer.getOutputMetrics()) { + computersByOutputMetric.put(outputMetric, computer); + } + for (String inputMetric : computer.getInputMetrics()) { + computersByInputMetric.put(inputMetric, computer); + } + } + ToComputerByKey toComputerByOutputMetricKey = new ToComputerByKey(computersByOutputMetric); + ToComputerByKey toComputerByInputMetricKey = new ToComputerByKey(computersByInputMetric); + + DirectAcyclicGraph dag = new DirectAcyclicGraph(); + for (MeasureComputer computer : computers) { + dag.add(computer); + for (MeasureComputer dependency : getDependencies(computer, toComputerByOutputMetricKey)) { + dag.add(computer, dependency); + } + for (MeasureComputer generates : getDependents(computer, toComputerByInputMetricKey)) { + dag.add(generates, computer); + } + } + return FluentIterable.from(dag.sort()); + } + + private void validateInputMetrics(List computers) { + Set pluginMetricKeys = FluentIterable.from(Arrays.asList(metricsRepositories)) + .transformAndConcat(MetricsToMetricList.INSTANCE) + .transform(MetricToKey.INSTANCE) + .toSet(); + // TODO would be nice to generate an error containing all bad input metrics + for (MeasureComputer computer : computers) { + for (String metric : computer.getInputMetrics()) { + if (!pluginMetricKeys.contains(metric) && !CORE_METRIC_KEYS.contains(metric)) { + throw new IllegalStateException(String.format("Metric '%s' cannot be used as an input metric as it's no a core metric and no plugin declare this metric", metric)); + } + } + } + } + + private static Iterable getDependencies(MeasureComputer measureComputer, ToComputerByKey toComputerByOutputMetricKey) { + // Remove null computer because a computer can depend on a metric that is only generated by a sensor or on a core metrics + return FluentIterable.from(measureComputer.getInputMetrics()).transform(toComputerByOutputMetricKey).filter(Predicates.notNull()); + } + + private static Iterable getDependents(MeasureComputer measureComputer, ToComputerByKey toComputerByInputMetricKey) { + return FluentIterable.from(measureComputer.getInputMetrics()).transform(toComputerByInputMetricKey); + } + + private static class ToComputerByKey implements Function { + private final Map computersByMetric; + + private ToComputerByKey(Map computersByMetric) { + this.computersByMetric = computersByMetric; + } + + @Override + public MeasureComputer apply(@Nonnull String metricKey) { + return computersByMetric.get(metricKey); + } + } + + private enum MetricToKey implements Function { + INSTANCE; + + @Nullable + @Override + public String apply(@Nonnull Metric input) { + return input.key(); + } + } + + private enum MetricsToMetricList implements Function> { + INSTANCE; + + @Override + public List apply(@Nonnull Metrics input) { + return input.getMetrics(); + } + } + + @Override + public String getDescription() { + return "Feed measure computers"; + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureComputersHolderImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureComputersHolderImplTest.java new file mode 100644 index 00000000000..a42ce3ed72c --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/MeasureComputersHolderImplTest.java @@ -0,0 +1,73 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.measure; + +import com.google.common.collect.ImmutableList; +import java.util.Collections; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.ce.measure.MeasureComputer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class MeasureComputersHolderImplTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private MeasureComputersHolderImpl underTest = new MeasureComputersHolderImpl(); + + @Test + public void get_measure_computers() throws Exception { + MeasureComputer measureComputer = mock(MeasureComputer.class); + underTest.setMeasureComputers(Collections.singletonList(measureComputer)); + + assertThat(underTest.getMeasureComputers()).containsOnly(measureComputer); + } + + @Test + public void get_measure_computers_throws_ISE_if_not_initialized() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Measure computers have not been initialized yet"); + + underTest.getMeasureComputers(); + } + + @Test + public void set_measure_computers_supports_empty_arg_is_empty() { + underTest.setMeasureComputers(ImmutableList.of()); + + assertThat(underTest.getMeasureComputers()).isEmpty(); + } + + @Test + public void set_measure_computers_throws_ISE_if_already_initialized() { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Measure computers have already been initialized"); + + MeasureComputer measureComputer = mock(MeasureComputer.class); + underTest.setMeasureComputers(Collections.singletonList(measureComputer)); + underTest.setMeasureComputers(Collections.singletonList(measureComputer)); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/ComponentImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/ComponentImplTest.java new file mode 100644 index 00000000000..6c133a2b1f6 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/ComponentImplTest.java @@ -0,0 +1,122 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.measure.api; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.ce.measure.Component; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ComponentImplTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void create_project() throws Exception { + ComponentImpl component = new ComponentImpl("Project", Component.Type.PROJECT, null); + + assertThat(component.getType()).isEqualTo(Component.Type.PROJECT); + } + + @Test + public void create_source_file() throws Exception { + ComponentImpl component = new ComponentImpl("File", Component.Type.FILE, new ComponentImpl.FileAttributesImpl("xoo", false)); + + assertThat(component.getType()).isEqualTo(Component.Type.FILE); + assertThat(component.getFileAttributes().getLanguageKey()).isEqualTo("xoo"); + assertThat(component.getFileAttributes().isUnitTest()).isFalse(); + } + + @Test + public void create_test_file() throws Exception { + ComponentImpl component = new ComponentImpl("File", Component.Type.FILE, new ComponentImpl.FileAttributesImpl(null, true)); + + assertThat(component.getType()).isEqualTo(Component.Type.FILE); + assertThat(component.getFileAttributes().isUnitTest()).isTrue(); + assertThat(component.getFileAttributes().getLanguageKey()).isNull(); + } + + @Test + public void fail_with_ISE_when_calling_get_file_attributes_on_not_file() throws Exception { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Only component of type FILE have a FileAttributes object"); + + ComponentImpl component = new ComponentImpl("Project", Component.Type.PROJECT, null); + component.getFileAttributes(); + } + + @Test + public void fail_with_IAE_when_trying_to_create_a_file_without_file_attributes() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("omponent of type FILE must have a FileAttributes object"); + + new ComponentImpl("File", Component.Type.FILE, null); + } + + @Test + public void fail_with_IAE_when_trying_to_create_not_a_file_with_file_attributes() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Only component of type FILE have a FileAttributes object"); + + new ComponentImpl("Project", Component.Type.PROJECT, new ComponentImpl.FileAttributesImpl(null, true)); + } + + @Test + public void fail_with_NPE_when_creating_component_without_key() throws Exception { + thrown.expect(NullPointerException.class); + thrown.expectMessage("Key cannot be null"); + + new ComponentImpl(null, Component.Type.PROJECT, null); + } + + @Test + public void fail_with_NPE_when_creating_component_without_type() throws Exception { + thrown.expect(NullPointerException.class); + thrown.expectMessage("Type cannot be null"); + + new ComponentImpl("Project", null, null); + } + + @Test + public void test_equals_and_hashcode() throws Exception { + ComponentImpl component = new ComponentImpl("Project1", Component.Type.PROJECT, null); + ComponentImpl sameComponent = new ComponentImpl("Project1", Component.Type.PROJECT, null); + ComponentImpl anotherComponent = new ComponentImpl("Project2", Component.Type.PROJECT, null); + + assertThat(component).isEqualTo(component); + assertThat(component).isEqualTo(sameComponent); + assertThat(component).isNotEqualTo(anotherComponent); + assertThat(component).isNotEqualTo(null); + + assertThat(component.hashCode()).isEqualTo(component.hashCode()); + assertThat(component.hashCode()).isEqualTo(sameComponent.hashCode()); + assertThat(component.hashCode()).isNotEqualTo(anotherComponent.hashCode()); + } + + @Test + public void test_to_string() throws Exception { + assertThat(new ComponentImpl("File", Component.Type.FILE, new ComponentImpl.FileAttributesImpl("xoo", true)).toString()) + .isEqualTo("ComponentImpl{key=File, type='FILE', fileAttributes=FileAttributesImpl{languageKey='xoo', unitTest=true}}"); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerImplTest.java new file mode 100644 index 00000000000..a813d8efd65 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerImplTest.java @@ -0,0 +1,204 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.measure.api; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.ce.measure.MeasureComputer; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MeasureComputerImplTest { + + private static final MeasureComputer.Implementation DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION = new MeasureComputer.Implementation() { + @Override + public void compute(MeasureComputer.Implementation.Context ctx) { + // Nothing here for this test + } + + @Override + public String toString() { + return "Test implementation"; + } + }; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void build_measure_computer() throws Exception { + String inputMetric = "ncloc"; + String outputMetric = "comment_density"; + MeasureComputer measureComputer = new MeasureComputerImpl.MeasureComputerBuilderImpl() + .setInputMetrics(inputMetric) + .setOutputMetrics(outputMetric) + .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION) + .build(); + + assertThat(measureComputer.getInputMetrics()).containsOnly(inputMetric); + assertThat(measureComputer.getOutputMetrics()).containsOnly(outputMetric); + assertThat(measureComputer.getImplementation()).isEqualTo(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION); + } + + @Test + public void build_measure_computer_with_multiple_metrics() throws Exception { + String[] inputMetrics = {"ncloc", "comment"}; + String[] outputMetrics = {"comment_density_1", "comment_density_2"}; + MeasureComputer measureComputer = new MeasureComputerImpl.MeasureComputerBuilderImpl() + .setInputMetrics(inputMetrics) + .setOutputMetrics(outputMetrics) + .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION) + .build(); + + assertThat(measureComputer.getInputMetrics()).containsOnly(inputMetrics); + assertThat(measureComputer.getOutputMetrics()).containsOnly(outputMetrics); + assertThat(measureComputer.getImplementation()).isEqualTo(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION); + } + + @Test + public void fail_with_IAE_when_no_input_metrics() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("At least one input metric must be defined"); + + new MeasureComputerImpl.MeasureComputerBuilderImpl() + .setOutputMetrics("comment_density_1", "comment_density_2") + .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION) + .build(); + } + + @Test + public void fail_with_IAE_when_null_input_metrics() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("At least one input metric must be defined"); + + new MeasureComputerImpl.MeasureComputerBuilderImpl() + .setInputMetrics(null) + .setOutputMetrics("comment_density_1", "comment_density_2") + .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION); + } + + @Test + public void fail_with_IAE_with_empty_input_metrics() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("At least one input metric must be defined"); + + new MeasureComputerImpl.MeasureComputerBuilderImpl() + .setInputMetrics() + .setOutputMetrics("comment_density_1", "comment_density_2") + .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION); + } + + @Test + public void fail_with_IAE_when_no_output_metrics() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("At least one output metric must be defined"); + + new MeasureComputerImpl.MeasureComputerBuilderImpl() + .setInputMetrics("ncloc", "comment") + .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION) + .build(); + } + + @Test + public void fail_with_IAE_when_null_output_metrics() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("At least one output metric must be defined"); + + new MeasureComputerImpl.MeasureComputerBuilderImpl() + .setInputMetrics("ncloc", "comment") + .setOutputMetrics(null) + .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION); + } + + @Test + public void fail_with_IAE_with_empty_output_metrics() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("At least one output metric must be defined"); + + new MeasureComputerImpl.MeasureComputerBuilderImpl() + .setInputMetrics("ncloc", "comment") + .setOutputMetrics() + .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION); + } + + @Test + public void fail_with_IAE_when_no_implementation() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("The implementation is missing"); + + new MeasureComputerImpl.MeasureComputerBuilderImpl() + .setInputMetrics("ncloc", "comment") + .setOutputMetrics("comment_density_1", "comment_density_2") + .build(); + } + + @Test + public void fail_with_IAE_when_null_implementation() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("The implementation is missing"); + + new MeasureComputerImpl.MeasureComputerBuilderImpl() + .setInputMetrics("ncloc", "comment") + .setOutputMetrics("comment_density_1", "comment_density_2") + .setImplementation(null); + } + + @Test + public void test_equals_and_hashcode() throws Exception { + MeasureComputer computer = new MeasureComputerImpl.MeasureComputerBuilderImpl() + .setInputMetrics("ncloc", "comment") + .setOutputMetrics("comment_density_1", "comment_density_2") + .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION) + .build(); + + MeasureComputer sameComputer = new MeasureComputerImpl.MeasureComputerBuilderImpl() + .setInputMetrics("comment", "ncloc") + .setOutputMetrics("comment_density_2", "comment_density_1") + .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION) + .build(); + + MeasureComputer anotherComputer = new MeasureComputerImpl.MeasureComputerBuilderImpl() + .setInputMetrics("comment") + .setOutputMetrics("debt") + .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION) + .build(); + + assertThat(computer).isEqualTo(computer); + assertThat(computer).isEqualTo(sameComputer); + assertThat(computer).isNotEqualTo(anotherComputer); + assertThat(computer).isNotEqualTo(null); + + assertThat(computer.hashCode()).isEqualTo(computer.hashCode()); + assertThat(computer.hashCode()).isEqualTo(sameComputer.hashCode()); + assertThat(computer.hashCode()).isNotEqualTo(anotherComputer.hashCode()); + } + + @Test + public void test_to_string() throws Exception { + assertThat(new MeasureComputerImpl.MeasureComputerBuilderImpl() + .setInputMetrics("ncloc", "comment") + .setOutputMetrics("comment_density_1", "comment_density_2") + .setImplementation(DEFAULT_MEASURE_COMPUTER_IMPLEMENTATION) + .build().toString()) + .isEqualTo("MeasureComputerImpl{inputMetricKeys=[ncloc, comment], outputMetrics=[comment_density_1, comment_density_2], implementation=Test implementation}"); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerImplementationContextTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerImplementationContextTest.java new file mode 100644 index 00000000000..de1e872c1cf --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerImplementationContextTest.java @@ -0,0 +1,252 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.measure.api; + +import com.google.common.base.Optional; +import java.util.Collections; +import java.util.Set; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.ce.measure.Component; +import org.sonar.api.ce.measure.MeasureComputer; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.server.computation.batch.TreeRootHolderRule; +import org.sonar.server.computation.component.ProjectSettingsRepository; +import org.sonar.server.computation.measure.Measure; +import org.sonar.server.computation.measure.MeasureRepositoryRule; +import org.sonar.server.computation.metric.Metric; +import org.sonar.server.computation.metric.MetricImpl; +import org.sonar.server.computation.metric.MetricRepositoryRule; + +import static com.google.common.collect.ImmutableSet.of; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.guava.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.api.measures.CoreMetrics.COMMENT_LINES_KEY; +import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY; +import static org.sonar.server.computation.component.DumbComponent.builder; +import static org.sonar.server.computation.measure.Measure.newMeasureBuilder; + +public class MeasureComputerImplementationContextTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private static final String INT_METRIC_KEY = "int_metric_key"; + private static final String DOUBLE_METRIC_KEY = "double_metric_key"; + private static final String LONG_METRIC_KEY = "long_metric_key"; + private static final String STRING_METRIC_KEY = "string_metric_key"; + + private static final int PROJECT_REF = 1; + private static final int FILE_1_REF = 12341; + private static final String FILE_1_KEY = "fileKey"; + private static final int FILE_2_REF = 12342; + + @Rule + public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule() + .setRoot(builder(org.sonar.server.computation.component.Component.Type.PROJECT, PROJECT_REF).setKey("project") + .addChildren( + builder(org.sonar.server.computation.component.Component.Type.FILE, FILE_1_REF).setKey(FILE_1_KEY).build(), + builder(org.sonar.server.computation.component.Component.Type.FILE, FILE_2_REF).setKey("fileKey2").build() + ).build()); + + @Rule + public MetricRepositoryRule metricRepository = new MetricRepositoryRule() + .add(1, CoreMetrics.NCLOC) + .add(new MetricImpl(2, INT_METRIC_KEY, "int metric", Metric.MetricType.INT)) + .add(new MetricImpl(3, DOUBLE_METRIC_KEY, "double metric", Metric.MetricType.FLOAT)) + .add(new MetricImpl(4, LONG_METRIC_KEY, "long metric", Metric.MetricType.MILLISEC)) + .add(new MetricImpl(5, STRING_METRIC_KEY, "string metric", Metric.MetricType.STRING)) + ; + + @Rule + public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository); + + ProjectSettingsRepository projectSettingsRepository = mock(ProjectSettingsRepository.class); + + @Test + public void get_component() throws Exception { + MeasureComputer.Implementation.Context underTest = newContext(FILE_1_REF); + assertThat(underTest.getComponent().getType()).isEqualTo(Component.Type.FILE); + } + + @Test + public void get_string_settings() throws Exception { + org.sonar.api.config.Settings serverSettings = new org.sonar.api.config.Settings(); + serverSettings.setProperty("prop", "value"); + when(projectSettingsRepository.getProjectSettings(FILE_1_KEY)).thenReturn(serverSettings); + + MeasureComputer.Implementation.Context underTest = newContext(FILE_1_REF); + assertThat(underTest.getSettings().getString("prop")).isEqualTo("value"); + assertThat(underTest.getSettings().getString("unknown")).isNull(); + } + + @Test + public void get_string_array_settings() throws Exception { + org.sonar.api.config.Settings serverSettings = new org.sonar.api.config.Settings(); + serverSettings.setProperty("prop", "1,3.4,8,50"); + when(projectSettingsRepository.getProjectSettings(FILE_1_KEY)).thenReturn(serverSettings); + + MeasureComputer.Implementation.Context underTest = newContext(FILE_1_REF); + assertThat(underTest.getSettings().getStringArray("prop")).containsExactly("1", "3.4", "8", "50"); + assertThat(underTest.getSettings().getStringArray("unknown")).isEmpty(); + } + + @Test + public void get_measure() throws Exception { + measureRepository.addRawMeasure(FILE_1_REF, NCLOC_KEY, newMeasureBuilder().create(10)); + + MeasureComputer.Implementation.Context underTest = newContext(FILE_1_REF, of(NCLOC_KEY), of(COMMENT_LINES_KEY)); + assertThat(underTest.getMeasure(NCLOC_KEY).getIntValue()).isEqualTo(10); + } + + @Test + public void fail_with_IAE_when_get_measure_is_called_on_metric_not_in_input_list() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Only metrics in [another metric] can be used to load measures"); + + MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of("another metric"), of("debt")); + underTest.getMeasure(NCLOC_KEY); + } + + @Test + public void get_children_measures() throws Exception { + measureRepository.addRawMeasure(FILE_1_REF, NCLOC_KEY, newMeasureBuilder().create(10)); + measureRepository.addRawMeasure(FILE_2_REF, NCLOC_KEY, newMeasureBuilder().create(12)); + + MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of(NCLOC_KEY), of(COMMENT_LINES_KEY)); + assertThat(underTest.getChildrenMeasures(NCLOC_KEY)).hasSize(2); + assertThat(underTest.getChildrenMeasures(NCLOC_KEY)).extracting("intValue").containsOnly(10, 12); + } + + @Test + public void get_children_measures_when_one_child_has_no_value() throws Exception { + measureRepository.addRawMeasure(FILE_1_REF, NCLOC_KEY, newMeasureBuilder().create(10)); + // No data on file 2 + + MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of(NCLOC_KEY), of(COMMENT_LINES_KEY)); + assertThat(underTest.getChildrenMeasures(NCLOC_KEY)).extracting("intValue").containsOnly(10); + } + + @Test + public void not_fail_to_get_children_measures_on_output_metric() throws Exception { + measureRepository.addRawMeasure(FILE_1_REF, INT_METRIC_KEY, newMeasureBuilder().create(10)); + + MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of(NCLOC_KEY), of(INT_METRIC_KEY)); + assertThat(underTest.getChildrenMeasures(INT_METRIC_KEY)).hasSize(1); + assertThat(underTest.getChildrenMeasures(INT_METRIC_KEY)).extracting("intValue").containsOnly(10); + } + + @Test + public void fail_with_IAE_when_get_children_measures_is_called_on_metric_not_in_input_list() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Only metrics in [another metric] can be used to load measures"); + + MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of("another metric"), of("debt")); + underTest.getChildrenMeasures(NCLOC_KEY); + } + + @Test + public void add_int_measure_create_measure_of_type_int_with_right_value() throws Exception { + MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of(NCLOC_KEY), of(INT_METRIC_KEY)); + underTest.addMeasure(INT_METRIC_KEY, 10); + + Optional measure = measureRepository.getAddedRawMeasure(PROJECT_REF, INT_METRIC_KEY); + assertThat(measure).isPresent(); + assertThat(measure.get().getIntValue()).isEqualTo(10); + } + + @Test + public void add_double_measure_create_measure_of_type_double_with_right_value() throws Exception { + MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of(NCLOC_KEY), of(DOUBLE_METRIC_KEY)); + underTest.addMeasure(DOUBLE_METRIC_KEY, 10d); + + Optional measure = measureRepository.getAddedRawMeasure(PROJECT_REF, DOUBLE_METRIC_KEY); + assertThat(measure).isPresent(); + assertThat(measure.get().getDoubleValue()).isEqualTo(10d); + } + + @Test + public void add_long_measure_create_measure_of_type_long_with_right_value() throws Exception { + MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of(NCLOC_KEY), of(LONG_METRIC_KEY)); + underTest.addMeasure(LONG_METRIC_KEY, 10L); + + Optional measure = measureRepository.getAddedRawMeasure(PROJECT_REF, LONG_METRIC_KEY); + assertThat(measure).isPresent(); + assertThat(measure.get().getLongValue()).isEqualTo(10L); + } + + @Test + public void add_string_measure_create_measure_of_type_string_with_right_value() throws Exception { + MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of(NCLOC_KEY), of(STRING_METRIC_KEY)); + underTest.addMeasure(STRING_METRIC_KEY, "data"); + + Optional measure = measureRepository.getAddedRawMeasure(PROJECT_REF, STRING_METRIC_KEY); + assertThat(measure).isPresent(); + assertThat(measure.get().getStringValue()).isEqualTo("data"); + } + + @Test + public void fail_with_IAE_when_add_measure_is_called_on_metric_not_in_output_list() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Only metrics in [int_metric_key] can be used to add measures"); + + MeasureComputer.Implementation.Context underTest = newContext(PROJECT_REF, of(NCLOC_KEY), of(INT_METRIC_KEY)); + underTest.addMeasure(DOUBLE_METRIC_KEY, 10); + } + + @Test + public void fail_with_unsupported_operation_when_adding_measure_that_already_exists() throws Exception { + thrown.expect(UnsupportedOperationException.class); + thrown.expectMessage("A measure on metric 'int_metric_key' already exists on component 'fileKey'"); + + measureRepository.addRawMeasure(FILE_1_REF, INT_METRIC_KEY, newMeasureBuilder().create(20)); + + MeasureComputer.Implementation.Context underTest = newContext(FILE_1_REF, of(NCLOC_KEY), of(INT_METRIC_KEY)); + underTest.addMeasure(INT_METRIC_KEY, 10); + } + + private MeasureComputer.Implementation.Context newContext(int componentRef) { + return newContext(componentRef, Collections.emptySet(), Collections.emptySet()); + } + + private MeasureComputer.Implementation.Context newContext(int componentRef, final Set inputMetrics, final Set outputMetrics) { + MeasureComputer measureComputer = new MeasureComputer() { + @Override + public Set getInputMetrics() { + return inputMetrics; + } + + @Override + public Set getOutputMetrics() { + return outputMetrics; + } + + @Override + public Implementation getImplementation() { + return null; + } + }; + return new MeasureComputerImplementationContext(treeRootHolder.getComponentByRef(componentRef), measureComputer, projectSettingsRepository, measureRepository, metricRepository); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContextTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContextTest.java new file mode 100644 index 00000000000..d68eacdee10 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureComputerProviderContextTest.java @@ -0,0 +1,153 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.measure.api; + +import com.google.common.collect.ImmutableSet; +import java.util.Collections; +import java.util.Set; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.ce.measure.MeasureComputer; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MeasureComputerProviderContextTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + MeasureComputerProviderContext underTest = new MeasureComputerProviderContext(); + + @Test + public void return_empty_list() throws Exception { + assertThat(underTest.getMeasureComputers()).isEmpty(); + } + + @Test + public void add_measure_computer() throws Exception { + underTest.add(newMeasureComputer("debt_density")); + + assertThat(underTest.getMeasureComputers()).hasSize(1); + } + + @Test + public void add_measure_computers_sharing_same_input_metrics() throws Exception { + underTest.add(newMeasureComputer(new String[]{"ncloc"}, new String[]{"debt_density"})); + underTest.add(newMeasureComputer(new String[]{"ncloc"}, new String[]{"comment"})); + + assertThat(underTest.getMeasureComputers()).hasSize(2); + } + + @Test + public void fail_with_unsupported_operation_exception_when_output_metric_have_already_been_registered() throws Exception { + thrown.expect(UnsupportedOperationException.class); + thrown.expectMessage("The output metric 'debt_density' is already declared by another computer. This computer has these input metrics '[ncloc, debt]' and these output metrics '[debt_by_line, debt_density]"); + + underTest.add(newMeasureComputer("debt_by_line","debt_density")); + underTest.add(newMeasureComputer("total_debt", "debt_density")); + } + + @Test + public void fail_with_unsupported_operation_exception_when_multiple_output_metrics_have_already_been_registered() throws Exception { + thrown.expect(UnsupportedOperationException.class); + thrown.expectMessage("The output metric 'debt_density' is already declared by another computer. This computer has these input metrics '[ncloc, debt]' and these output metrics '[debt_density]'. " + + "The output metric 'debt_by_line' is already declared by another computer. This computer has these input metrics '[ncloc, debt]' and these output metrics '[debt_by_line]"); + + underTest.add(newMeasureComputer("debt_by_line")); + underTest.add(newMeasureComputer("debt_density")); + underTest.add(newMeasureComputer("debt_by_line", "debt_density")); + } + + @Test + public void create_measure_computer_without_using_the_builder() throws Exception { + // Create a instance of MeasureComputer without using the builder + MeasureComputer measureComputer = new MeasureComputer() { + @Override + public Set getInputMetrics() { + return ImmutableSet.of("ncloc", "debt"); + } + + @Override + public Set getOutputMetrics() { + return ImmutableSet.of("debt_density"); + } + + @Override + public Implementation getImplementation() { + return new MeasureComputer.Implementation() { + @Override + public void compute(Context ctx) { + } + }; + } + }; + + underTest.add(measureComputer); + assertThat(underTest.getMeasureComputers()).hasSize(1); + } + + @Test + public void fail_with_IAE_when_creating_measure_computer_without_using_the_builder_but_with_invalid_output_metrics() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("At least one output metric must be defined"); + + MeasureComputer measureComputer = new MeasureComputer() { + @Override + public Set getInputMetrics() { + return ImmutableSet.of("ncloc", "debt"); + } + + @Override + public Set getOutputMetrics() { + return Collections.emptySet(); + } + + @Override + public Implementation getImplementation() { + return new MeasureComputer.Implementation() { + @Override + public void compute(Context ctx) {} + }; + } + }; + + underTest.add(measureComputer); + } + + private MeasureComputer newMeasureComputer(String... outputMetrics) { + return newMeasureComputer(new String[]{"ncloc", "debt"}, outputMetrics); + } + + private MeasureComputer newMeasureComputer(String[] inputMetrics, String[] outputMetrics) { + return new MeasureComputerImpl.MeasureComputerBuilderImpl() + .setInputMetrics(inputMetrics) + .setOutputMetrics(outputMetrics) + .setImplementation(new MeasureComputer.Implementation() { + @Override + public void compute(Context ctx) { + // Nothing to do here for this test + } + }) + .build(); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureImplTest.java new file mode 100644 index 00000000000..cd7d24d690f --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/measure/api/MeasureImplTest.java @@ -0,0 +1,118 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.measure.api; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.server.computation.measure.Measure; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MeasureImplTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void get_int_value() throws Exception { + MeasureImpl measure = new MeasureImpl(Measure.newMeasureBuilder().create(1)); + assertThat(measure.getIntValue()).isEqualTo(1); + } + + @Test + public void fail_with_ISE_when_not_int_value() throws Exception { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Value can not be converted to int because current value type is a DOUBLE"); + + MeasureImpl measure = new MeasureImpl(Measure.newMeasureBuilder().create(1d)); + measure.getIntValue(); + } + + @Test + public void get_double_value() throws Exception { + MeasureImpl measure = new MeasureImpl(Measure.newMeasureBuilder().create(1d)); + assertThat(measure.getDoubleValue()).isEqualTo(1d); + } + + @Test + public void fail_with_ISE_when_not_double_value() throws Exception { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Value can not be converted to double because current value type is a INT"); + + MeasureImpl measure = new MeasureImpl(Measure.newMeasureBuilder().create(1)); + measure.getDoubleValue(); + } + + @Test + public void get_long_value() throws Exception { + MeasureImpl measure = new MeasureImpl(Measure.newMeasureBuilder().create(1L)); + assertThat(measure.getLongValue()).isEqualTo(1L); + } + + @Test + public void fail_with_ISE_when_not_long_value() throws Exception { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Value can not be converted to long because current value type is a STRING"); + + MeasureImpl measure = new MeasureImpl(Measure.newMeasureBuilder().create("value")); + measure.getLongValue(); + } + + @Test + public void get_string_value() throws Exception { + MeasureImpl measure = new MeasureImpl(Measure.newMeasureBuilder().create("value")); + assertThat(measure.getStringValue()).isEqualTo("value"); + } + + @Test + public void fail_with_ISE_when_not_string_value() throws Exception { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Value can not be converted to string because current value type is a LONG"); + + MeasureImpl measure = new MeasureImpl(Measure.newMeasureBuilder().create(1L)); + measure.getStringValue(); + } + + @Test + public void fail_with_ISE_when_creating_measure_with_no_value() throws Exception { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Only following types are allowed [INT, LONG, DOUBLE, STRING]"); + + new MeasureImpl(Measure.newMeasureBuilder().createNoValue()); + } + + @Test + public void fail_with_ISE_when_creating_measure_with_not_allowed_value() throws Exception { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Only following types are allowed [INT, LONG, DOUBLE, STRING]"); + + new MeasureImpl(Measure.newMeasureBuilder().create(Measure.Level.ERROR)); + } + + @Test + public void fail_with_NPE_when_creating_measure_with_null_measure() throws Exception { + thrown.expect(NullPointerException.class); + thrown.expectMessage("Measure couldn't be null"); + + new MeasureImpl(null); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputePluginMeasuresStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputePluginMeasuresStepTest.java new file mode 100644 index 00000000000..4b3c42f8340 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputePluginMeasuresStepTest.java @@ -0,0 +1,160 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.step; + +import java.util.Collections; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.ce.measure.Measure; +import org.sonar.api.ce.measure.MeasureComputer; +import org.sonar.server.computation.batch.TreeRootHolderRule; +import org.sonar.server.computation.measure.MeasureComputersHolderImpl; +import org.sonar.server.computation.measure.MeasureRepositoryRule; +import org.sonar.server.computation.measure.api.MeasureComputerImpl; +import org.sonar.server.computation.metric.MetricRepositoryRule; + +import static com.google.common.collect.Lists.newArrayList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.api.measures.CoreMetrics.COMMENT_LINES; +import static org.sonar.api.measures.CoreMetrics.COMMENT_LINES_KEY; +import static org.sonar.api.measures.CoreMetrics.NCLOC; +import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY; +import static org.sonar.server.computation.component.Component.Type.DIRECTORY; +import static org.sonar.server.computation.component.Component.Type.FILE; +import static org.sonar.server.computation.component.Component.Type.MODULE; +import static org.sonar.server.computation.component.Component.Type.PROJECT; +import static org.sonar.server.computation.component.DumbComponent.builder; +import static org.sonar.server.computation.measure.Measure.newMeasureBuilder; +import static org.sonar.server.computation.measure.MeasureRepoEntry.entryOf; +import static org.sonar.server.computation.measure.MeasureRepoEntry.toEntries; + +public class ComputePluginMeasuresStepTest { + + private static final String NEW_METRIC_KEY = "new_metric_key"; + private static final String NEW_METRIC_NAME = "new metric name"; + + private static final org.sonar.api.measures.Metric NEW_METRIC = new org.sonar.api.measures.Metric.Builder(NEW_METRIC_KEY, NEW_METRIC_NAME, + org.sonar.api.measures.Metric.ValueType.INT) + .create(); + + private static final int ROOT_REF = 1; + private static final int MODULE_REF = 12; + private static final int DIRECTORY_REF = 123; + private static final int FILE_1_REF = 1231; + private static final int FILE_2_REF = 1232; + + @Rule + public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); + + @Rule + public MetricRepositoryRule metricRepository = new MetricRepositoryRule() + .add(NCLOC) + .add(COMMENT_LINES) + .add(NEW_METRIC); + + @Rule + public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository); + + @Before + public void setUp() throws Exception { + treeRootHolder.setRoot( + builder(PROJECT, ROOT_REF).setKey("project") + .addChildren( + builder(MODULE, MODULE_REF).setKey("module") + .addChildren( + builder(DIRECTORY, DIRECTORY_REF).setKey("directory") + .addChildren( + builder(FILE, FILE_1_REF).setKey("file1").build(), + builder(FILE, FILE_2_REF).setKey("file2").build() + ).build() + ).build() + ).build()); + } + + MeasureComputersHolderImpl measureComputersHolder = new MeasureComputersHolderImpl(); + + @Test + public void compute_plugin_measure() throws Exception { + measureRepository.addRawMeasure(FILE_1_REF, NCLOC_KEY, newMeasureBuilder().create(10)); + measureRepository.addRawMeasure(FILE_1_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(2)); + measureRepository.addRawMeasure(FILE_2_REF, NCLOC_KEY, newMeasureBuilder().create(40)); + measureRepository.addRawMeasure(FILE_2_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(5)); + measureRepository.addRawMeasure(DIRECTORY_REF, NCLOC_KEY, newMeasureBuilder().create(50)); + measureRepository.addRawMeasure(DIRECTORY_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(7)); + measureRepository.addRawMeasure(MODULE_REF, NCLOC_KEY, newMeasureBuilder().create(50)); + measureRepository.addRawMeasure(MODULE_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(7)); + measureRepository.addRawMeasure(ROOT_REF, NCLOC_KEY, newMeasureBuilder().create(50)); + measureRepository.addRawMeasure(ROOT_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(7)); + + measureComputersHolder.setMeasureComputers(newArrayList( + new MeasureComputerImpl.MeasureComputerBuilderImpl() + .setInputMetrics(NCLOC_KEY, COMMENT_LINES_KEY) + .setOutputMetrics(NEW_METRIC_KEY) + .setImplementation( + new MeasureComputer.Implementation() { + @Override + public void compute(Context ctx) { + Measure ncloc = ctx.getMeasure(NCLOC_KEY); + Measure comment = ctx.getMeasure(COMMENT_LINES_KEY); + if (ncloc != null && comment != null) { + ctx.addMeasure(NEW_METRIC_KEY, ncloc.getIntValue() + comment.getIntValue()); + } + } + } + ) + .build() + )); + ComputationStep underTest = new ComputePluginMeasuresStep(treeRootHolder, metricRepository, measureRepository, null, measureComputersHolder); + underTest.execute(); + + assertThat(toEntries(measureRepository.getAddedRawMeasures(FILE_1_REF))).containsOnly(entryOf(NEW_METRIC_KEY, newMeasureBuilder().create(12))); + assertThat(toEntries(measureRepository.getAddedRawMeasures(FILE_2_REF))).containsOnly(entryOf(NEW_METRIC_KEY, newMeasureBuilder().create(45))); + assertThat(toEntries(measureRepository.getAddedRawMeasures(DIRECTORY_REF))).containsOnly(entryOf(NEW_METRIC_KEY, newMeasureBuilder().create(57))); + assertThat(toEntries(measureRepository.getAddedRawMeasures(MODULE_REF))).containsOnly(entryOf(NEW_METRIC_KEY, newMeasureBuilder().create(57))); + assertThat(toEntries(measureRepository.getAddedRawMeasures(ROOT_REF))).containsOnly(entryOf(NEW_METRIC_KEY, newMeasureBuilder().create(57))); + } + + @Test + public void nothing_to_compute() throws Exception { + measureRepository.addRawMeasure(FILE_1_REF, NCLOC_KEY, newMeasureBuilder().create(10)); + measureRepository.addRawMeasure(FILE_1_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(2)); + measureRepository.addRawMeasure(FILE_2_REF, NCLOC_KEY, newMeasureBuilder().create(40)); + measureRepository.addRawMeasure(FILE_2_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(5)); + measureRepository.addRawMeasure(DIRECTORY_REF, NCLOC_KEY, newMeasureBuilder().create(50)); + measureRepository.addRawMeasure(DIRECTORY_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(7)); + measureRepository.addRawMeasure(MODULE_REF, NCLOC_KEY, newMeasureBuilder().create(50)); + measureRepository.addRawMeasure(MODULE_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(7)); + measureRepository.addRawMeasure(ROOT_REF, NCLOC_KEY, newMeasureBuilder().create(50)); + measureRepository.addRawMeasure(ROOT_REF, COMMENT_LINES_KEY, newMeasureBuilder().create(7)); + + measureComputersHolder.setMeasureComputers(Collections.emptyList()); + ComputationStep underTest = new ComputePluginMeasuresStep(treeRootHolder, metricRepository, measureRepository, null, measureComputersHolder); + underTest.execute(); + + assertThat(toEntries(measureRepository.getAddedRawMeasures(FILE_1_REF))).isEmpty(); + assertThat(toEntries(measureRepository.getAddedRawMeasures(FILE_2_REF))).isEmpty(); + assertThat(toEntries(measureRepository.getAddedRawMeasures(DIRECTORY_REF))).isEmpty(); + assertThat(toEntries(measureRepository.getAddedRawMeasures(MODULE_REF))).isEmpty(); + assertThat(toEntries(measureRepository.getAddedRawMeasures(ROOT_REF))).isEmpty(); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/FeedMeasureComputersTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/FeedMeasureComputersTest.java new file mode 100644 index 00000000000..aab334c6818 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/FeedMeasureComputersTest.java @@ -0,0 +1,216 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.step; + +import com.google.common.collect.Lists; +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.ce.measure.MeasureComputer; +import org.sonar.api.ce.measure.MeasureComputerProvider; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Metric; +import org.sonar.api.measures.Metrics; +import org.sonar.server.computation.measure.MeasureComputersHolderImpl; + +import static com.google.common.collect.Lists.newArrayList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.util.Arrays.array; +import static org.mockito.Mockito.mock; +import static org.sonar.api.measures.Metric.ValueType.DATA; +import static org.sonar.api.measures.Metric.ValueType.FLOAT; +import static org.sonar.api.measures.Metric.ValueType.INT; +import static org.sonar.api.measures.Metric.ValueType.MILLISEC; + +public class FeedMeasureComputersTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private static final String NEW_METRIC_1 = "metric1"; + private static final String NEW_METRIC_2 = "metric2"; + private static final String NEW_METRIC_3 = "metric3"; + private static final String NEW_METRIC_4 = "metric4"; + + MeasureComputersHolderImpl holder = new MeasureComputersHolderImpl(); + + @Test + public void support_core_metrics_as_input_metrics() throws Exception { + MeasureComputer.Implementation implementation = mock(MeasureComputer.Implementation.class); + MeasureComputerProvider[] providers = new MeasureComputerProvider[] { + new NewMeasureComputerProvider( + array(CoreMetrics.NCLOC_KEY), + array(NEW_METRIC_1), + implementation), + }; + ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), providers); + underTest.execute(); + + assertThat(holder.getMeasureComputers()).hasSize(1); + } + + @Test + public void support_plugin_metrics_as_input_metrics() throws Exception { + MeasureComputer.Implementation implementation = mock(MeasureComputer.Implementation.class); + MeasureComputerProvider[] providers = new MeasureComputerProvider[] { + new NewMeasureComputerProvider( + array(NEW_METRIC_1), + array(NEW_METRIC_2), + implementation), + }; + ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), providers); + underTest.execute(); + + assertThat(holder.getMeasureComputers()).hasSize(1); + } + + @Test + public void sort_computers() throws Exception { + MeasureComputer.Implementation implementation1 = mock(MeasureComputer.Implementation.class); + MeasureComputer.Implementation implementation2 = mock(MeasureComputer.Implementation.class); + MeasureComputer.Implementation implementation3 = mock(MeasureComputer.Implementation.class); + + MeasureComputerProvider[] providers = new MeasureComputerProvider[] { + // Should be the last to be executed + new NewMeasureComputerProvider( + array(NEW_METRIC_3), + array(NEW_METRIC_4), + implementation3), + // Should be the first to be executed + new NewMeasureComputerProvider( + array(NEW_METRIC_1), + array(NEW_METRIC_2), + implementation1), + // Should be the second to be executed + new NewMeasureComputerProvider( + array(NEW_METRIC_2), + array(NEW_METRIC_3), + implementation2) + }; + + ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), providers); + underTest.execute(); + + List computers = newArrayList(holder.getMeasureComputers()); + assertThat(computers).hasSize(3); + assertThat(computers.get(0).getImplementation()).isEqualTo(implementation1); + assertThat(computers.get(1).getImplementation()).isEqualTo(implementation2); + assertThat(computers.get(2).getImplementation()).isEqualTo(implementation3); + } + + @Test + public void fail_with_ISE_when_input_metric_is_unknown() throws Exception { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Metric 'unknown' cannot be used as an input metric as it's no a core metric and no plugin declare this metric"); + + MeasureComputerProvider[] providers = new MeasureComputerProvider[] { + new NewMeasureComputerProvider( + array("unknown"), + array(NEW_METRIC_4), + mock(MeasureComputer.Implementation.class)), + }; + + ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), providers); + underTest.execute(); + } + + @Test + public void not_fail_if_input_metrics_are_same_as_output_metrics() throws Exception { + MeasureComputer.Implementation implementation = mock(MeasureComputer.Implementation.class); + MeasureComputerProvider[] providers = new MeasureComputerProvider[] { + new NewMeasureComputerProvider( + array(NEW_METRIC_1), + array(NEW_METRIC_1), + implementation), + }; + ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics()), providers); + underTest.execute(); + + assertThat(holder.getMeasureComputers()).hasSize(1); + } + + @Test + public void return_empty_list_when_no_measure_computers() throws Exception { + ComputationStep underTest = new FeedMeasureComputers(holder, array(new TestMetrics())); + underTest.execute(); + + assertThat(holder.getMeasureComputers()).isEmpty(); + } + + @Test + public void support_no_plugin_metrics() throws Exception { + MeasureComputer.Implementation implementation = mock(MeasureComputer.Implementation.class); + MeasureComputerProvider[] providers = new MeasureComputerProvider[] { + new NewMeasureComputerProvider( + array(CoreMetrics.NCLOC_KEY), + array(CoreMetrics.COMMENT_LINES_KEY), + implementation), + }; + ComputationStep underTest = new FeedMeasureComputers(holder, providers); + underTest.execute(); + + assertThat(holder.getMeasureComputers()).hasSize(1); + } + + @Test + public void return_empty_list_when_no_metrics_neither_measure_computers() throws Exception { + ComputationStep underTest = new FeedMeasureComputers(holder); + underTest.execute(); + + assertThat(holder.getMeasureComputers()).isEmpty(); + } + + private static class NewMeasureComputerProvider implements MeasureComputerProvider { + + private final String[] inputMetrics; + private final String[] outputMetrics; + private final MeasureComputer.Implementation measureComputerImplementation; + + public NewMeasureComputerProvider(String[] inputMetrics, String[] outputMetrics, MeasureComputer.Implementation measureComputerImplementation) { + this.inputMetrics = inputMetrics; + this.outputMetrics = outputMetrics; + this.measureComputerImplementation = measureComputerImplementation; + } + + @Override + public void register(Context ctx) { + ctx.add(ctx.newMeasureComputerBuilder() + .setInputMetrics(inputMetrics) + .setOutputMetrics(outputMetrics) + .setImplementation(measureComputerImplementation) + .build()); + } + } + + private static class TestMetrics implements Metrics { + @Override + public List getMetrics() { + return Lists.newArrayList( + new Metric.Builder(NEW_METRIC_1, "metric1", DATA).create(), + new Metric.Builder(NEW_METRIC_2, "metric2", MILLISEC).create(), + new Metric.Builder(NEW_METRIC_3, "metric3", INT).create(), + new Metric.Builder(NEW_METRIC_4, "metric4", FLOAT).create() + ); + } + } + +} -- 2.39.5