From: Simon Brandhof Date: Thu, 17 Apr 2014 21:33:27 +0000 (+0200) Subject: SONAR-4969 Do not mix batch and server classpaths X-Git-Tag: 4.4-RC1~1445^2~11 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=f2dd46cf5745f7c14f2bce20f5131e4e242f8c7c;p=sonarqube.git SONAR-4969 Do not mix batch and server classpaths --- diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java index a87d01690a6..b8770197f47 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java @@ -20,36 +20,84 @@ package org.sonar.plugins.core; import com.google.common.collect.ImmutableList; -import org.sonar.api.*; +import org.sonar.api.CoreProperties; +import org.sonar.api.Properties; +import org.sonar.api.Property; +import org.sonar.api.PropertyType; +import org.sonar.api.SonarPlugin; import org.sonar.api.checks.NoSonarFilter; -import org.sonar.api.config.PropertyDefinition; -import org.sonar.api.resources.Qualifiers; -import org.sonar.batch.components.PastSnapshotFinder; -import org.sonar.batch.debt.IssueChangelogDebtCalculator; -import org.sonar.batch.issue.ignore.IssueExclusionsConfiguration; import org.sonar.core.timemachine.Periods; import org.sonar.plugins.core.batch.IndexProjectPostJob; import org.sonar.plugins.core.charts.DistributionAreaChart; import org.sonar.plugins.core.charts.DistributionBarChart; import org.sonar.plugins.core.charts.XradarChart; import org.sonar.plugins.core.colorizers.JavaColorizerFormat; -import org.sonar.plugins.core.dashboards.*; -import org.sonar.plugins.core.issue.*; -import org.sonar.plugins.core.issue.notification.*; +import org.sonar.plugins.core.dashboards.GlobalDefaultDashboard; +import org.sonar.plugins.core.dashboards.ProjectDefaultDashboard; +import org.sonar.plugins.core.dashboards.ProjectHotspotDashboard; +import org.sonar.plugins.core.dashboards.ProjectIssuesDashboard; +import org.sonar.plugins.core.dashboards.ProjectTimeMachineDashboard; +import org.sonar.plugins.core.issue.CountFalsePositivesDecorator; +import org.sonar.plugins.core.issue.CountUnresolvedIssuesDecorator; +import org.sonar.plugins.core.issue.InitialOpenIssuesSensor; +import org.sonar.plugins.core.issue.InitialOpenIssuesStack; +import org.sonar.plugins.core.issue.IssueHandlers; +import org.sonar.plugins.core.issue.IssueTracking; +import org.sonar.plugins.core.issue.IssueTrackingDecorator; +import org.sonar.plugins.core.issue.IssuesDensityDecorator; +import org.sonar.plugins.core.issue.WeightedIssuesDecorator; +import org.sonar.plugins.core.issue.notification.ChangesOnMyIssueNotificationDispatcher; +import org.sonar.plugins.core.issue.notification.IssueChangesEmailTemplate; +import org.sonar.plugins.core.issue.notification.NewFalsePositiveNotificationDispatcher; +import org.sonar.plugins.core.issue.notification.NewIssuesEmailTemplate; +import org.sonar.plugins.core.issue.notification.NewIssuesNotificationDispatcher; +import org.sonar.plugins.core.issue.notification.SendIssueNotificationsPostJob; import org.sonar.plugins.core.measurefilters.MyFavouritesFilter; import org.sonar.plugins.core.measurefilters.ProjectFilter; import org.sonar.plugins.core.notifications.alerts.NewAlerts; import org.sonar.plugins.core.security.ApplyProjectRolesDecorator; -import org.sonar.plugins.core.sensors.*; -import org.sonar.plugins.core.technicaldebt.DebtDecorator; -import org.sonar.plugins.core.technicaldebt.NewDebtDecorator; -import org.sonar.plugins.core.timemachine.*; +import org.sonar.plugins.core.sensors.BranchCoverageDecorator; +import org.sonar.plugins.core.sensors.CommentDensityDecorator; +import org.sonar.plugins.core.sensors.CoverageDecorator; +import org.sonar.plugins.core.sensors.CoverageMeasurementFilter; +import org.sonar.plugins.core.sensors.DirectoriesDecorator; +import org.sonar.plugins.core.sensors.FileHashSensor; +import org.sonar.plugins.core.sensors.FilesDecorator; +import org.sonar.plugins.core.sensors.ItBranchCoverageDecorator; +import org.sonar.plugins.core.sensors.ItCoverageDecorator; +import org.sonar.plugins.core.sensors.ItLineCoverageDecorator; +import org.sonar.plugins.core.sensors.LineCoverageDecorator; +import org.sonar.plugins.core.sensors.ManualMeasureDecorator; +import org.sonar.plugins.core.sensors.OverallBranchCoverageDecorator; +import org.sonar.plugins.core.sensors.OverallCoverageDecorator; +import org.sonar.plugins.core.sensors.OverallLineCoverageDecorator; +import org.sonar.plugins.core.sensors.ProfileEventsSensor; +import org.sonar.plugins.core.sensors.ProjectLinksSensor; +import org.sonar.plugins.core.sensors.UnitTestDecorator; +import org.sonar.plugins.core.sensors.VersionEventsSensor; +import org.sonar.plugins.core.timemachine.NewCoverageAggregator; +import org.sonar.plugins.core.timemachine.NewCoverageFileAnalyzer; +import org.sonar.plugins.core.timemachine.NewItCoverageFileAnalyzer; +import org.sonar.plugins.core.timemachine.NewOverallCoverageFileAnalyzer; +import org.sonar.plugins.core.timemachine.TendencyDecorator; +import org.sonar.plugins.core.timemachine.TimeMachineConfigurationPersister; +import org.sonar.plugins.core.timemachine.VariationDecorator; import org.sonar.plugins.core.web.TestsViewer; import org.sonar.plugins.core.widgets.*; -import org.sonar.plugins.core.widgets.issues.*; -import org.sonar.plugins.core.widgets.measures.*; +import org.sonar.plugins.core.widgets.issues.ActionPlansWidget; +import org.sonar.plugins.core.widgets.issues.FalsePositiveIssuesWidget; +import org.sonar.plugins.core.widgets.issues.IssueFilterWidget; +import org.sonar.plugins.core.widgets.issues.IssuesWidget; +import org.sonar.plugins.core.widgets.issues.MyUnresolvedIssuesWidget; +import org.sonar.plugins.core.widgets.issues.UnresolvedIssuesPerAssigneeWidget; +import org.sonar.plugins.core.widgets.issues.UnresolvedIssuesStatusesWidget; +import org.sonar.plugins.core.widgets.measures.MeasureFilterAsBubbleChartWidget; +import org.sonar.plugins.core.widgets.measures.MeasureFilterAsCloudWidget; +import org.sonar.plugins.core.widgets.measures.MeasureFilterAsHistogramWidget; +import org.sonar.plugins.core.widgets.measures.MeasureFilterAsPieChartWidget; +import org.sonar.plugins.core.widgets.measures.MeasureFilterListWidget; +import org.sonar.plugins.core.widgets.measures.MeasureFilterTreemapWidget; -import java.util.Arrays; import java.util.List; @Properties({ @@ -292,11 +340,6 @@ public final class CorePlugin extends SonarPlugin { NewFalsePositiveNotificationDispatcher.class, NewFalsePositiveNotificationDispatcher.newMetadata(), - // technical debt - DebtDecorator.class, - NewDebtDecorator.class, - IssueChangelogDebtCalculator.class, - // batch ProfileEventsSensor.class, ProjectLinksSensor.class, @@ -334,64 +377,7 @@ public final class CorePlugin extends SonarPlugin { NewAlerts.class, NewAlerts.newMetadata()); - extensions.addAll(ExclusionProperties.definitions()); - extensions.addAll(IssueExclusionsConfiguration.getPropertyDefinitions()); - extensions.addAll(CoverageMeasurementFilter.getPropertyDefinitions()); - extensions.addAll(PastSnapshotFinder.getPropertyDefinitions()); - extensions.addAll(DebtDecorator.definitions()); - extensions.addAll(propertyDefinitions()); - return extensions.build(); } - static List propertyDefinitions() { - return Arrays.asList( - PropertyDefinition.builder(CoreProperties.CORE_VIOLATION_LOCALE_PROPERTY) - .defaultValue("en") - .name("Locale used for issue messages") - .description("Deprecated property. Keep default value for backward compatibility.") - .hidden() - .build(), - PropertyDefinition.builder(CoreProperties.CORE_ALLOW_USERS_TO_SIGNUP_PROPERTY) - .defaultValue("" + CoreProperties.CORE_ALLOW_USERS_TO_SIGNUP_DEAULT_VALUE) - .name("Allow users to sign up online") - .description("Users can sign up online.") - .type(PropertyType.BOOLEAN) - .category(CoreProperties.CATEGORY_SECURITY) - .build(), - - PropertyDefinition.builder(CoreProperties.CORE_DEFAULT_GROUP) - .defaultValue(CoreProperties.CORE_DEFAULT_GROUP_DEFAULT_VALUE) - .name("Default user group") - .description("Any new users will automatically join this group.") - .category(CoreProperties.CATEGORY_SECURITY) - .build(), - - PropertyDefinition.builder(CoreProperties.CORE_IMPORT_SOURCES_PROPERTY) - .defaultValue("" + CoreProperties.CORE_IMPORT_SOURCES_DEFAULT_VALUE) - .name("Import sources") - .description("Set to false if sources should not be imported and therefore not available in the Web UI (e.g. for security reasons).") - .type(PropertyType.BOOLEAN) - .onQualifiers(Qualifiers.PROJECT, Qualifiers.MODULE) - .category(CoreProperties.CATEGORY_SECURITY) - .build(), - - PropertyDefinition.builder(CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY) - .defaultValue("" + CoreProperties.CORE_FORCE_AUTHENTICATION_DEFAULT_VALUE) - .name("Force user authentication") - .description("Forcing user authentication stops un-logged users to access SonarQube.") - .type(PropertyType.BOOLEAN) - .category(CoreProperties.CATEGORY_SECURITY) - .build(), - - PropertyDefinition.builder(CoreProperties.CORE_PREVENT_AUTOMATIC_PROJECT_CREATION) - .defaultValue(Boolean.toString(false)) - .name("Prevent automatic project creation") - .description("Set to true to prevent automatic project creation at first analysis and force project provisioning.") - .type(PropertyType.BOOLEAN) - .category(CoreProperties.CATEGORY_SECURITY) - .build() - ); - } - } diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/ExclusionProperties.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/ExclusionProperties.java deleted file mode 100644 index b169a572965..00000000000 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/ExclusionProperties.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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.plugins.core; - -import com.google.common.collect.ImmutableList; -import org.sonar.api.CoreProperties; -import org.sonar.api.config.PropertyDefinition; -import org.sonar.api.resources.Qualifiers; - -import java.util.List; - -class ExclusionProperties { - - private ExclusionProperties() { - // only static stuff - } - - static List definitions() { - return ImmutableList.of( - PropertyDefinition.builder(CoreProperties.PROJECT_INCLUSIONS_PROPERTY) - .name("Source File Inclusions") - .multiValues(true) - .category(CoreProperties.CATEGORY_EXCLUSIONS) - .subCategory(CoreProperties.SUBCATEGORY_FILES_EXCLUSIONS) - .onQualifiers(Qualifiers.PROJECT) - .index(3) - .build(), - PropertyDefinition.builder(CoreProperties.PROJECT_TEST_INCLUSIONS_PROPERTY) - .name("Test File Inclusions") - .multiValues(true) - .category(CoreProperties.CATEGORY_EXCLUSIONS) - .subCategory(CoreProperties.SUBCATEGORY_FILES_EXCLUSIONS) - .onQualifiers(Qualifiers.PROJECT) - .index(5) - .build(), - PropertyDefinition.builder(CoreProperties.GLOBAL_EXCLUSIONS_PROPERTY) - .name("Global Source File Exclusions") - .multiValues(true) - .category(CoreProperties.CATEGORY_EXCLUSIONS) - .subCategory(CoreProperties.SUBCATEGORY_FILES_EXCLUSIONS) - .index(0) - .build(), - PropertyDefinition.builder(CoreProperties.GLOBAL_TEST_EXCLUSIONS_PROPERTY) - .name("Global Test File Exclusions") - .multiValues(true) - .category(CoreProperties.CATEGORY_EXCLUSIONS) - .subCategory(CoreProperties.SUBCATEGORY_FILES_EXCLUSIONS) - .index(1) - .build(), - PropertyDefinition.builder(CoreProperties.PROJECT_EXCLUSIONS_PROPERTY) - .name("Source File Exclusions") - .multiValues(true) - .category(CoreProperties.CATEGORY_EXCLUSIONS) - .subCategory(CoreProperties.SUBCATEGORY_FILES_EXCLUSIONS) - .onQualifiers(Qualifiers.PROJECT) - .index(2) - .build(), - PropertyDefinition.builder(CoreProperties.PROJECT_TEST_EXCLUSIONS_PROPERTY) - .name("Test File Exclusions") - .multiValues(true) - .category(CoreProperties.CATEGORY_EXCLUSIONS) - .subCategory(CoreProperties.SUBCATEGORY_FILES_EXCLUSIONS) - .onQualifiers(Qualifiers.PROJECT) - .index(4) - .build(), - PropertyDefinition.builder(CoreProperties.CORE_SKIPPED_MODULES_PROPERTY) - .name("Exclude Modules") - .description("Maven artifact ids of modules to exclude.") - .multiValues(true) - .category(CoreProperties.CATEGORY_EXCLUSIONS) - .subCategory(CoreProperties.SUBCATEGORY_FILES_EXCLUSIONS) - .onlyOnQualifiers(Qualifiers.PROJECT) - .index(0) - .build(), - PropertyDefinition.builder(CoreProperties.CORE_INCLUDED_MODULES_PROPERTY) - .name("Include Modules") - .description("Maven artifact ids of modules to include.") - .multiValues(true) - .category(CoreProperties.CATEGORY_EXCLUSIONS) - .subCategory(CoreProperties.SUBCATEGORY_FILES_EXCLUSIONS) - .onlyOnQualifiers(Qualifiers.PROJECT) - .index(1) - .build()); - } -} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CoverageMeasurementFilter.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CoverageMeasurementFilter.java index e2bcea80dce..eeda60396a9 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CoverageMeasurementFilter.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CoverageMeasurementFilter.java @@ -23,34 +23,26 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableSet; -import org.sonar.api.CoreProperties; -import org.sonar.api.PropertyType; -import org.sonar.api.config.PropertyDefinition; import org.sonar.api.config.Settings; import org.sonar.api.measures.Measure; import org.sonar.api.measures.Metric; -import org.sonar.api.resources.Qualifiers; import org.sonar.api.resources.Resource; import org.sonar.api.utils.WildcardPattern; import org.sonar.core.measure.MeasurementFilter; import java.util.Collection; import java.util.Iterator; -import java.util.List; public class CoverageMeasurementFilter implements MeasurementFilter { - public static final String PROPERTY_COVERAGE_EXCLUSIONS = "sonar.coverage.exclusions"; - public static final String PROPERTY_COVERAGE_INCLUSIONS = "sonar.coverage.inclusions"; - private final Settings settings; private final ImmutableSet coverageMetrics; private Collection resourcePatterns; public CoverageMeasurementFilter(Settings settings, - CoverageDecorator coverageDecorator, - LineCoverageDecorator lineCoverageDecorator, - BranchCoverageDecorator branchCoverageDecorator) { + CoverageDecorator coverageDecorator, + LineCoverageDecorator lineCoverageDecorator, + BranchCoverageDecorator branchCoverageDecorator) { this.settings = settings; this.coverageMetrics = ImmutableSet.builder() .addAll(coverageDecorator.generatedMetrics()) @@ -88,22 +80,10 @@ public class CoverageMeasurementFilter implements MeasurementFilter { @VisibleForTesting final void initPatterns() { - Builder builder = ImmutableList.builder(); - for (String pattern : settings.getStringArray(PROPERTY_COVERAGE_EXCLUSIONS)) { + Builder builder = ImmutableList.builder(); + for (String pattern : settings.getStringArray("sonar.coverage.exclusions")) { builder.add(WildcardPattern.create(pattern)); } resourcePatterns = builder.build(); } - - public static List getPropertyDefinitions() { - return ImmutableList.of( - PropertyDefinition.builder(PROPERTY_COVERAGE_EXCLUSIONS) - .category(CoreProperties.CATEGORY_EXCLUSIONS) - .subCategory(CoreProperties.SUBCATEGORY_COVERAGE_EXCLUSIONS) - .type(PropertyType.STRING) - .multiValues(true) - .onQualifiers(Qualifiers.PROJECT, Qualifiers.MODULE) - .build() - ); - } } diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/DebtDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/DebtDecorator.java deleted file mode 100644 index 2caf164d9c2..00000000000 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/DebtDecorator.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * 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.plugins.core.technicaldebt; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; -import org.sonar.api.CoreProperties; -import org.sonar.api.PropertyType; -import org.sonar.api.batch.*; -import org.sonar.api.batch.rule.Rule; -import org.sonar.api.batch.rule.Rules; -import org.sonar.api.component.ResourcePerspectives; -import org.sonar.api.config.PropertyDefinition; -import org.sonar.api.issue.Issuable; -import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.measures.*; -import org.sonar.api.resources.Project; -import org.sonar.api.resources.Resource; -import org.sonar.api.resources.ResourceUtils; -import org.sonar.api.rule.RuleKey; -import org.sonar.api.rules.RuleFinder; -import org.sonar.api.technicaldebt.batch.Characteristic; -import org.sonar.api.technicaldebt.batch.TechnicalDebtModel; - -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Maps.newHashMap; - -/** - * Decorator that computes the technical debt metric - */ -@DependsUpon(DecoratorBarriers.ISSUES_TRACKED) -public final class DebtDecorator implements Decorator { - - private final ResourcePerspectives perspectives; - private final TechnicalDebtModel model; - private final Rules rules; - - /** - * ruleFinder is needed to load "old" rule in order to persist rule measure - */ - private final RuleFinder ruleFinder; - - public DebtDecorator(ResourcePerspectives perspectives, TechnicalDebtModel model, Rules rules, RuleFinder ruleFinder) { - this.perspectives = perspectives; - this.model = model; - this.rules = rules; - this.ruleFinder = ruleFinder; - } - - public boolean shouldExecuteOnProject(Project project) { - return true; - } - - @DependedUpon - public List generatesMetrics() { - return Arrays.asList(CoreMetrics.TECHNICAL_DEBT); - } - - public void decorate(Resource resource, DecoratorContext context) { - Issuable issuable = perspectives.as(Issuable.class, resource); - if (issuable != null && shouldSaveMeasure(context)) { - List issues = newArrayList(issuable.issues()); - saveMeasures(context, issues); - } - } - - private void saveMeasures(DecoratorContext context, List issues) { - Long total = 0L; - SumMap ruleDebts = new SumMap(); - SumMap characteristicDebts = new SumMap(); - - // Aggregate rules debt from current issues (and populate current characteristic debt) - for (Issue issue : issues) { - Long debt = ((DefaultIssue) issue).debtInMinutes(); - total += computeDebt(debt, issue.ruleKey(), ruleDebts, characteristicDebts); - } - - // Aggregate rules debt from children (and populate children characteristics debt) - for (Measure measure : context.getChildrenMeasures(MeasuresFilters.rules(CoreMetrics.TECHNICAL_DEBT))) { - Long debt = measure.getValue().longValue(); - RuleMeasure ruleMeasure = (RuleMeasure) measure; - total += computeDebt(debt, ruleMeasure.getRule().ruleKey(), ruleDebts, characteristicDebts); - } - - context.saveMeasure(CoreMetrics.TECHNICAL_DEBT, total.doubleValue()); - saveOnRule(context, ruleDebts); - for (Characteristic characteristic : model.characteristics()) { - Long debt = characteristicDebts.get(characteristic); - saveCharacteristicMeasure(context, characteristic, debt != null ? debt.doubleValue() : 0d, false); - } - } - - private Long computeDebt(@Nullable Long debt, RuleKey ruleKey, SumMap ruleDebts, SumMap characteristicDebts) { - if (debt != null) { - Rule rule = rules.find(ruleKey); - if (rule != null) { - String characteristicKey = rule.debtSubCharacteristic(); - if (characteristicKey != null) { - Characteristic characteristic = model.characteristicByKey(characteristicKey); - if (characteristic != null) { - ruleDebts.add(ruleKey, debt); - characteristicDebts.add(characteristic, debt); - propagateTechnicalDebtInParents(characteristic.parent(), debt, characteristicDebts); - return debt; - } - } - } - } - return 0L; - } - - private void propagateTechnicalDebtInParents(@Nullable Characteristic characteristic, long value, SumMap characteristicDebts) { - if (characteristic != null) { - characteristicDebts.add(characteristic, value); - propagateTechnicalDebtInParents(characteristic.parent(), value, characteristicDebts); - } - } - - private void saveOnRule(DecoratorContext context, SumMap ruleDebts) { - for (Map.Entry entry : ruleDebts.entrySet()) { - org.sonar.api.rules.Rule oldRule = ruleFinder.findByKey(entry.getKey()); - if (oldRule != null) { - saveRuleMeasure(context, oldRule, entry.getValue().doubleValue(), ResourceUtils.isEntity(context.getResource())); - } - } - } - - @VisibleForTesting - void saveCharacteristicMeasure(DecoratorContext context, Characteristic characteristic, Double value, boolean inMemory) { - // we need the value on projects (root or module) even if value==0 in order to display correctly the SQALE history chart (see SQALE-122) - // BUT we don't want to save zero-values for non top-characteristics (see SQALE-147) - if (value > 0.0 || (ResourceUtils.isProject(context.getResource()) && characteristic.isRoot())) { - Measure measure = new Measure(CoreMetrics.TECHNICAL_DEBT); - measure.setCharacteristic(characteristic); - saveMeasure(context, measure, value, inMemory); - } - } - - @VisibleForTesting - void saveRuleMeasure(DecoratorContext context, org.sonar.api.rules.Rule rule, Double value, boolean inMemory) { - // we need the value on projects (root or module) even if value==0 in order to display correctly the SQALE history chart (see SQALE-122) - // BUT we don't want to save zero-values for non top-characteristics (see SQALE-147) - if (value > 0.0) { - RuleMeasure measure = new RuleMeasure(CoreMetrics.TECHNICAL_DEBT, rule, null, null); - saveMeasure(context, measure, value, inMemory); - } - } - - private void saveMeasure(DecoratorContext context, Measure measure, Double value, boolean inMemory) { - measure.setValue(value); - if (inMemory) { - measure.setPersistenceMode(PersistenceMode.MEMORY); - } - context.saveMeasure(measure); - } - - private boolean shouldSaveMeasure(DecoratorContext context) { - return context.getMeasure(CoreMetrics.TECHNICAL_DEBT) == null; - } - - public static List definitions() { - return ImmutableList.of( - PropertyDefinition.builder(CoreProperties.HOURS_IN_DAY) - .name("Number of working hours in a day") - .type(PropertyType.INTEGER) - .defaultValue("8") - .category(CoreProperties.CATEGORY_TECHNICAL_DEBT) - .deprecatedKey("sqale.hoursInDay") - .build() - ); - } - - private static class SumMap { - private Map sumByKeys; - - public SumMap() { - sumByKeys = newHashMap(); - } - - public void add(@Nullable E key, Long value) { - if (key != null) { - Long currentValue = sumByKeys.get(key); - sumByKeys.put(key, currentValue != null ? currentValue + value : value); - } - } - - @CheckForNull - public Long get(E key) { - return sumByKeys.get(key); - } - - public Set> entrySet() { - return sumByKeys.entrySet(); - } - } -} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/NewDebtDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/NewDebtDecorator.java deleted file mode 100644 index f5f7062a584..00000000000 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/NewDebtDecorator.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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.plugins.core.technicaldebt; - -import com.google.common.collect.ImmutableList; -import org.sonar.api.batch.*; -import org.sonar.api.component.ResourcePerspectives; -import org.sonar.api.issue.Issuable; -import org.sonar.api.issue.Issue; -import org.sonar.api.measures.CoreMetrics; -import org.sonar.api.measures.Measure; -import org.sonar.api.measures.MeasureUtils; -import org.sonar.api.measures.Metric; -import org.sonar.api.resources.Project; -import org.sonar.api.resources.Resource; -import org.sonar.batch.components.Period; -import org.sonar.batch.components.TimeMachineConfiguration; -import org.sonar.batch.debt.IssueChangelogDebtCalculator; - -import javax.annotation.Nullable; - -import java.util.Collection; -import java.util.Date; -import java.util.List; - -import static com.google.common.collect.Lists.newArrayList; - -/** - * Decorator that computes the technical debt metric - */ -@DependsUpon(DecoratorBarriers.ISSUES_TRACKED) -public final class NewDebtDecorator implements Decorator { - - private final ResourcePerspectives perspectives; - private final TimeMachineConfiguration timeMachineConfiguration; - private final IssueChangelogDebtCalculator issueChangelogDebtCalculator; - - public NewDebtDecorator(ResourcePerspectives perspectives, TimeMachineConfiguration timeMachineConfiguration, - IssueChangelogDebtCalculator issueChangelogDebtCalculator) { - this.perspectives = perspectives; - this.timeMachineConfiguration = timeMachineConfiguration; - this.issueChangelogDebtCalculator = issueChangelogDebtCalculator; - } - - public boolean shouldExecuteOnProject(Project project) { - return true; - } - - @DependedUpon - public List generatesMetrics() { - return ImmutableList.of( - CoreMetrics.NEW_TECHNICAL_DEBT - ); - } - - public void decorate(Resource resource, DecoratorContext context) { - Issuable issuable = perspectives.as(Issuable.class, resource); - if (issuable != null && shouldSaveNewMetrics(context)) { - List issues = newArrayList(issuable.issues()); - saveMeasures(context, issues); - } - } - - private void saveMeasures(DecoratorContext context, Collection issues) { - Measure measure = new Measure(CoreMetrics.NEW_TECHNICAL_DEBT); - for (Period period : timeMachineConfiguration.periods()) { - Date periodDate = period.getDate(); - double value = calculateNewTechnicalDebtValue(issues, periodDate); - Collection children = context.getChildrenMeasures(measure.getMetric()); - double sum = MeasureUtils.sumOnVariation(true, period.getIndex(), children) + value; - measure.setVariation(period.getIndex(), sum); - } - context.saveMeasure(measure); - } - - private long calculateNewTechnicalDebtValue(Collection issues, @Nullable Date periodDate) { - long result = 0; - for (Issue issue : issues) { - Long debt = issueChangelogDebtCalculator.calculateNewTechnicalDebt(issue, periodDate); - if (debt != null) { - result += debt; - } - } - return result; - } - - private boolean shouldSaveNewMetrics(DecoratorContext context) { - return context.getMeasure(CoreMetrics.NEW_TECHNICAL_DEBT) == null; - } - -} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/package-info.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/package-info.java deleted file mode 100644 index defa4e01d36..00000000000 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/technicaldebt/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.plugins.core.technicaldebt; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/ExclusionPropertiesTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/ExclusionPropertiesTest.java deleted file mode 100644 index 42aed67cb00..00000000000 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/ExclusionPropertiesTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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.plugins.core; - -import org.junit.Test; -import org.sonar.api.CoreProperties; -import org.sonar.api.config.PropertyDefinition; - -import static org.fest.assertions.Assertions.assertThat; - -public class ExclusionPropertiesTest { - @Test - public void definitions() throws Exception { - assertThat(ExclusionProperties.definitions().size()).isGreaterThan(0); - for (PropertyDefinition definition : ExclusionProperties.definitions()) { - assertThat(definition.category()).isEqualTo(CoreProperties.CATEGORY_EXCLUSIONS); - } - } -} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/CoverageMeasurementFilterTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/CoverageMeasurementFilterTest.java index 8254fb28094..072dea5844e 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/CoverageMeasurementFilterTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/CoverageMeasurementFilterTest.java @@ -27,6 +27,7 @@ import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Measure; import org.sonar.api.resources.File; import org.sonar.api.resources.Resource; +import org.sonar.core.config.ExclusionProperties; import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -40,7 +41,7 @@ public class CoverageMeasurementFilterTest { @Before public void createFilter() { - settings = new Settings(new PropertyDefinitions(CoverageMeasurementFilter.getPropertyDefinitions())); + settings = new Settings(new PropertyDefinitions(ExclusionProperties.all())); filter = new CoverageMeasurementFilter(settings, new CoverageDecorator(), new LineCoverageDecorator(), new BranchCoverageDecorator()); } @@ -57,7 +58,7 @@ public class CoverageMeasurementFilterTest { Measure coverageMeasure = mock(Measure.class); when(coverageMeasure.getMetric()).thenReturn(CoreMetrics.LINES_TO_COVER); - settings.setProperty(CoverageMeasurementFilter.PROPERTY_COVERAGE_EXCLUSIONS, "src/org/polop/*"); + settings.setProperty("sonar.coverage.exclusions", "src/org/polop/*"); filter.initPatterns(); assertThat(filter.accept(resource, coverageMeasure)).isFalse(); } @@ -68,7 +69,7 @@ public class CoverageMeasurementFilterTest { Measure coverageMeasure = mock(Measure.class); when(coverageMeasure.getMetric()).thenReturn(CoreMetrics.COVERAGE); - settings.setProperty(CoverageMeasurementFilter.PROPERTY_COVERAGE_EXCLUSIONS, "src/org/other/*"); + settings.setProperty("sonar.coverage.exclusions", "src/org/other/*"); filter.initPatterns(); assertThat(filter.accept(resource, coverageMeasure)).isTrue(); } diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/DebtDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/DebtDecoratorTest.java deleted file mode 100644 index 2aac5804446..00000000000 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/DebtDecoratorTest.java +++ /dev/null @@ -1,351 +0,0 @@ -/* - * 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.plugins.core.technicaldebt; - -import com.google.common.collect.Lists; -import org.apache.commons.lang.ObjectUtils; -import org.apache.commons.lang.builder.ToStringBuilder; -import org.apache.commons.lang.builder.ToStringStyle; -import org.hamcrest.Description; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentMatcher; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; -import org.sonar.api.batch.DecoratorContext; -import org.sonar.api.batch.rule.Rules; -import org.sonar.api.batch.rule.internal.RulesBuilder; -import org.sonar.api.component.ResourcePerspectives; -import org.sonar.api.issue.Issuable; -import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.measures.*; -import org.sonar.api.resources.File; -import org.sonar.api.resources.Project; -import org.sonar.api.resources.Resource; -import org.sonar.api.rule.RuleKey; -import org.sonar.api.rules.RuleFinder; -import org.sonar.api.technicaldebt.batch.Characteristic; -import org.sonar.api.technicaldebt.batch.TechnicalDebtModel; -import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic; -import org.sonar.api.test.IsMeasure; -import org.sonar.api.utils.Duration; - -import java.util.Collections; - -import static com.google.common.collect.Lists.newArrayList; -import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.*; - -@RunWith(MockitoJUnitRunner.class) -public class DebtDecoratorTest { - - static final int HOURS_IN_DAY = 8; - - static final Long ONE_DAY_IN_MINUTES = 1L * HOURS_IN_DAY * 60; - - @Mock - DecoratorContext context; - - @Mock - Resource resource; - - @Mock - TechnicalDebtModel debtModel; - - @Mock - Issuable issuable; - - @Mock - ResourcePerspectives perspectives; - - @Mock - RuleFinder ruleFinder; - - RuleKey ruleKey1 = RuleKey.of("repo1", "rule1"); - RuleKey ruleKey2 = RuleKey.of("repo2", "rule2"); - Rules rules; - - DefaultCharacteristic efficiency = new DefaultCharacteristic().setKey("EFFICIENCY"); - DefaultCharacteristic memoryEfficiency = new DefaultCharacteristic().setKey("MEMORY_EFFICIENCY").setParent(efficiency); - - DefaultCharacteristic reusability = new DefaultCharacteristic().setKey("REUSABILITY"); - DefaultCharacteristic modularity = new DefaultCharacteristic().setKey("MODULARITY").setParent(reusability); - - DebtDecorator decorator; - - @Before - public void before() throws Exception { - when(perspectives.as(Issuable.class, resource)).thenReturn(issuable); - RulesBuilder rulesBuilder = new RulesBuilder(); - rulesBuilder.add(ruleKey1).setName("rule1").setDebtSubCharacteristic("MEMORY_EFFICIENCY"); - rulesBuilder.add(ruleKey2).setName("rule2").setDebtSubCharacteristic("MODULARITY"); - rules = rulesBuilder.build(); - - when(ruleFinder.findByKey(ruleKey1)).thenReturn(org.sonar.api.rules.Rule.create(ruleKey1.repository(), ruleKey1.rule())); - when(ruleFinder.findByKey(ruleKey2)).thenReturn(org.sonar.api.rules.Rule.create(ruleKey2.repository(), ruleKey2.rule())); - - when(debtModel.characteristics()).thenReturn(newArrayList(efficiency, memoryEfficiency, reusability, modularity)); - when(debtModel.characteristicByKey("EFFICIENCY")).thenReturn(efficiency); - when(debtModel.characteristicByKey("MEMORY_EFFICIENCY")).thenReturn(memoryEfficiency); - when(debtModel.characteristicByKey("REUSABILITY")).thenReturn(reusability); - when(debtModel.characteristicByKey("MODULARITY")).thenReturn(modularity); - - decorator = new DebtDecorator(perspectives, debtModel, rules, ruleFinder); - } - - @Test - public void generates_metrics() throws Exception { - assertThat(decorator.generatesMetrics()).hasSize(1); - } - - @Test - public void execute_on_project() throws Exception { - assertThat(decorator.shouldExecuteOnProject(null)).isTrue(); - } - - @Test - public void not_save_if_measure_already_computed() { - when(context.getMeasure(CoreMetrics.TECHNICAL_DEBT)).thenReturn(new Measure()); - - decorator.decorate(resource, context); - - verify(context, never()).saveMeasure(argThat(new IsMeasure(CoreMetrics.TECHNICAL_DEBT))); - } - - @Test - public void add_technical_debt_from_one_issue_and_no_parent() throws Exception { - Issue issue = createIssue("rule1", "repo1").setDebt(Duration.create(ONE_DAY_IN_MINUTES)); - when(issuable.issues()).thenReturn(newArrayList(issue)); - - decorator.decorate(resource, context); - - verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, ONE_DAY_IN_MINUTES.doubleValue()); - verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey1, ONE_DAY_IN_MINUTES.doubleValue()))); - } - - @Test - public void add_technical_debt_from_one_issue_without_debt() throws Exception { - Issue issue = createIssue("rule1", "repo1").setDebt(null); - when(issuable.issues()).thenReturn(newArrayList(issue)); - - decorator.decorate(resource, context); - - verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, 0.0); - } - - @Test - public void add_technical_debt_from_one_issue_and_propagate_to_parents() throws Exception { - Issue issue = createIssue("rule1", "repo1").setDebt(Duration.create(ONE_DAY_IN_MINUTES)); - when(issuable.issues()).thenReturn(newArrayList(issue)); - - decorator.decorate(resource, context); - - verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, ONE_DAY_IN_MINUTES.doubleValue()); - verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey1, ONE_DAY_IN_MINUTES.doubleValue()))); - verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, efficiency, ONE_DAY_IN_MINUTES.doubleValue()))); - verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, memoryEfficiency, ONE_DAY_IN_MINUTES.doubleValue()))); - } - - @Test - public void add_technical_debt_from_issues() throws Exception { - Long technicalDebt1 = ONE_DAY_IN_MINUTES; - Long technicalDebt2 = 2 * ONE_DAY_IN_MINUTES; - - Issue issue1 = createIssue("rule1", "repo1").setDebt(Duration.create(technicalDebt1)); - Issue issue2 = createIssue("rule1", "repo1").setDebt(Duration.create(technicalDebt1)); - Issue issue3 = createIssue("rule2", "repo2").setDebt(Duration.create(technicalDebt2)); - Issue issue4 = createIssue("rule2", "repo2").setDebt(Duration.create(technicalDebt2)); - when(issuable.issues()).thenReturn(newArrayList(issue1, issue2, issue3, issue4)); - - decorator.decorate(resource, context); - - verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, 6d * ONE_DAY_IN_MINUTES); - verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey1, 2d * ONE_DAY_IN_MINUTES))); - verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey2, 4d * ONE_DAY_IN_MINUTES))); - } - - @Test - public void add_technical_debt_from_current_and_children_measures() throws Exception { - Issue issue1 = createIssue("rule1", "repo1").setDebt(Duration.create(ONE_DAY_IN_MINUTES)); - Issue issue2 = createIssue("rule1", "repo1").setDebt(Duration.create(ONE_DAY_IN_MINUTES)); - when(issuable.issues()).thenReturn(newArrayList(issue1, issue2)); - - when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Lists.newArrayList( - new RuleMeasure(CoreMetrics.TECHNICAL_DEBT, - org.sonar.api.rules.Rule.create(ruleKey1.repository(), ruleKey1.rule()), null, null) - .setValue(5d * ONE_DAY_IN_MINUTES) - )); - decorator.decorate(resource, context); - - verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, 7d * ONE_DAY_IN_MINUTES); - verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey1, 7d * ONE_DAY_IN_MINUTES))); - verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, memoryEfficiency, 7d * ONE_DAY_IN_MINUTES))); - verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, efficiency, 7d * ONE_DAY_IN_MINUTES))); - } - - @Test - public void add_technical_debt_only_from_children_measures() throws Exception { - when(issuable.issues()).thenReturn(Collections.emptyList()); - - when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Lists.newArrayList( - new RuleMeasure(CoreMetrics.TECHNICAL_DEBT, - org.sonar.api.rules.Rule.create(ruleKey1.repository(), ruleKey1.rule()) - , null, null).setValue(5d * ONE_DAY_IN_MINUTES), - - new RuleMeasure(CoreMetrics.TECHNICAL_DEBT, - org.sonar.api.rules.Rule.create(ruleKey2.repository(), ruleKey2.rule()) - , null, null).setValue(10d * ONE_DAY_IN_MINUTES) - )); - decorator.decorate(resource, context); - - verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, 15d * ONE_DAY_IN_MINUTES); - verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey1, 5d * ONE_DAY_IN_MINUTES))); - verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey2, 10d * ONE_DAY_IN_MINUTES))); - verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, memoryEfficiency, 5d * ONE_DAY_IN_MINUTES))); - verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, efficiency, 5d * ONE_DAY_IN_MINUTES))); - verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, reusability, 10d * ONE_DAY_IN_MINUTES))); - verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, modularity, 10d * ONE_DAY_IN_MINUTES))); - } - - @Test - public void always_save_technical_debt_for_positive_values() throws Exception { - // for a project - DecoratorContext context = mock(DecoratorContext.class); - when(context.getResource()).thenReturn(new Project("foo")); - decorator.saveCharacteristicMeasure(context, (Characteristic) null, 12.0, false); - verify(context, times(1)).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT)); - - // or for a file - context = mock(DecoratorContext.class); - when(context.getResource()).thenReturn(new File("foo")); - decorator.saveCharacteristicMeasure(context, (Characteristic) null, 12.0, false); - verify(context, times(1)).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT)); - } - - @Test - public void always_save_technical_debt_for_project_if_top_characteristic() throws Exception { - DecoratorContext context = mock(DecoratorContext.class); - when(context.getResource()).thenReturn(new Project("foo")); - - // this is a top characteristic - DefaultCharacteristic rootCharacteristic = new DefaultCharacteristic().setKey("root"); - - decorator.saveCharacteristicMeasure(context, rootCharacteristic, 0.0, true); - verify(context, times(1)).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT).setCharacteristic(rootCharacteristic)); - } - - /** - * SQALE-147 - */ - @Test - public void never_save_technical_debt_for_project_if_not_top_characteristic() throws Exception { - DecoratorContext context = mock(DecoratorContext.class); - when(context.getResource()).thenReturn(new Project("foo")); - - DefaultCharacteristic rootCharacteristic = new DefaultCharacteristic().setKey("EFFICIENCY"); - DefaultCharacteristic characteristic = new DefaultCharacteristic().setKey("MEMORY_EFFICIENCY").setParent(rootCharacteristic); - - decorator.saveCharacteristicMeasure(context, characteristic, 0.0, true); - verify(context, never()).saveMeasure(any(Measure.class)); - } - - @Test - public void not_save_technical_debt_for_file_if_zero() throws Exception { - DecoratorContext context = mock(DecoratorContext.class); - when(context.getResource()).thenReturn(new File("foo")); - - decorator.saveCharacteristicMeasure(context, (Characteristic) null, 0.0, true); - verify(context, never()).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT)); - } - - @Test - public void check_definitions() { - assertThat(DebtDecorator.definitions()).hasSize(1); - } - - private DefaultIssue createIssue(String ruleKey, String repositoryKey) { - return new DefaultIssue().setRuleKey(RuleKey.of(repositoryKey, ruleKey)); - } - - class IsCharacteristicMeasure extends ArgumentMatcher { - Metric metric = null; - Characteristic characteristic = null; - Double value = null; - - public IsCharacteristicMeasure(Metric metric, Characteristic characteristic, Double value) { - this.metric = metric; - this.characteristic = characteristic; - this.value = value; - } - - @Override - public boolean matches(Object o) { - if (!(o instanceof Measure)) { - return false; - } - Measure m = (Measure) o; - return ObjectUtils.equals(metric, m.getMetric()) && - ObjectUtils.equals(characteristic, m.getCharacteristic()) && - ObjectUtils.equals(value, m.getValue()); - } - - @Override - public void describeTo(Description description) { - description.appendText(new StringBuilder() - .append("value=").append(value).append(",") - .append("characteristic=").append(characteristic.key()).append(",") - .append("metric=").append(metric.getKey()).toString()) - ; - } - } - - class IsRuleMeasure extends ArgumentMatcher { - Metric metric = null; - RuleKey ruleKey = null; - Double value = null; - - public IsRuleMeasure(Metric metric, RuleKey ruleKey, Double value) { - this.metric = metric; - this.ruleKey = ruleKey; - this.value = value; - } - - @Override - public boolean matches(Object o) { - if (!(o instanceof RuleMeasure)) { - return false; - } - RuleMeasure m = (RuleMeasure) o; - return ObjectUtils.equals(metric, m.getMetric()) && - ObjectUtils.equals(ruleKey.repository(), m.getRule().getRepositoryKey()) && - ObjectUtils.equals(ruleKey.rule(), m.getRule().getKey()) && - ObjectUtils.equals(value, m.getValue()); - } - - @Override - public void describeTo(Description description) { - description.appendText(ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE)); - } - } -} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/NewDebtDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/NewDebtDecoratorTest.java deleted file mode 100644 index a9391f96da2..00000000000 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/technicaldebt/NewDebtDecoratorTest.java +++ /dev/null @@ -1,374 +0,0 @@ -/* - * 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.plugins.core.technicaldebt; - -import org.apache.commons.lang.ObjectUtils; -import org.apache.commons.lang.time.DateUtils; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentMatcher; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; -import org.sonar.api.CoreProperties; -import org.sonar.api.batch.DecoratorContext; -import org.sonar.api.component.ResourcePerspectives; -import org.sonar.api.config.Settings; -import org.sonar.api.issue.Issuable; -import org.sonar.api.issue.Issue; -import org.sonar.api.issue.internal.DefaultIssue; -import org.sonar.api.issue.internal.FieldDiffs; -import org.sonar.api.measures.CoreMetrics; -import org.sonar.api.measures.Measure; -import org.sonar.api.measures.Metric; -import org.sonar.api.resources.Resource; -import org.sonar.api.test.IsMeasure; -import org.sonar.api.utils.Duration; -import org.sonar.batch.components.Period; -import org.sonar.batch.components.TimeMachineConfiguration; -import org.sonar.batch.debt.IssueChangelogDebtCalculator; - -import java.util.Date; - -import static com.google.common.collect.Lists.newArrayList; -import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Matchers.argThat; -import static org.mockito.Mockito.*; - -@RunWith(MockitoJUnitRunner.class) -public class NewDebtDecoratorTest { - - NewDebtDecorator decorator; - - @Mock - TimeMachineConfiguration timeMachineConfiguration; - - @Mock - Resource resource; - - @Mock - Issuable issuable; - - @Mock - DecoratorContext context; - - Date rightNow; - Date elevenDaysAgo; - Date tenDaysAgo; - Date nineDaysAgo; - Date fiveDaysAgo; - Date fourDaysAgo; - - static final int HOURS_IN_DAY = 8; - - static final Long ONE_DAY_IN_MINUTES = 1L * HOURS_IN_DAY * 60; - static final Long TWO_DAYS_IN_MINUTES = 2L * HOURS_IN_DAY * 60; - static final Long FIVE_DAYS_IN_MINUTES = 5L * HOURS_IN_DAY * 60; - - @Before - public void setup() { - Settings settings = new Settings(); - settings.setProperty(CoreProperties.HOURS_IN_DAY, HOURS_IN_DAY); - - ResourcePerspectives perspectives = mock(ResourcePerspectives.class); - when(perspectives.as(Issuable.class, resource)).thenReturn(issuable); - - rightNow = new Date(); - elevenDaysAgo = DateUtils.addDays(rightNow, -11); - tenDaysAgo = DateUtils.addDays(rightNow, -10); - nineDaysAgo = DateUtils.addDays(rightNow, -9); - fiveDaysAgo = DateUtils.addDays(rightNow, -5); - fourDaysAgo = DateUtils.addDays(rightNow, -4); - - when(timeMachineConfiguration.periods()).thenReturn(newArrayList(new Period(1, fiveDaysAgo), new Period(2, tenDaysAgo))); - - decorator = new NewDebtDecorator(perspectives, timeMachineConfiguration, new IssueChangelogDebtCalculator()); - } - - @Test - public void generates_metrics() throws Exception { - assertThat(decorator.generatesMetrics()).hasSize(1); - } - - @Test - public void execute_on_project() throws Exception { - assertThat(decorator.shouldExecuteOnProject(null)).isTrue(); - } - - @Test - public void save_on_one_issue_with_one_new_changelog() { - Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES)).setChanges( - newArrayList( - // changelog created at is null because it has just been created on the current analysis - new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(null) - ) - ); - when(issuable.issues()).thenReturn(newArrayList(issue)); - - decorator.decorate(resource, context); - - // remember : period1 is 5daysAgo, period2 is 10daysAgo - verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 1.0 * ONE_DAY_IN_MINUTES, 1.0 * ONE_DAY_IN_MINUTES))); - } - - @Test - public void save_on_one_issue_with_changelog() { - Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges( - newArrayList( - new FieldDiffs().setDiff("technicalDebt", TWO_DAYS_IN_MINUTES, FIVE_DAYS_IN_MINUTES).setCreationDate(null), - new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(fourDaysAgo) - ) - ); - when(issuable.issues()).thenReturn(newArrayList(issue)); - - decorator.decorate(resource, context); - - // remember : period1 is 5daysAgo, period2 is 10daysAgo - verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 4.0 * ONE_DAY_IN_MINUTES, 4.0 * ONE_DAY_IN_MINUTES))); - } - - @Test - public void save_on_one_issue_with_changelog_only_in_the_past() { - Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(ONE_DAY_IN_MINUTES)).setChanges( - newArrayList( - // Change before all periods - new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(elevenDaysAgo) - ) - ); - when(issuable.issues()).thenReturn(newArrayList(issue)); - - decorator.decorate(resource, context); - - // remember : period1 is 5daysAgo, period2 is 10daysAgo - verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 0.0))); - } - - @Test - public void save_on_one_issue_with_changelog_having_null_value() { - Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges( - newArrayList( - new FieldDiffs().setDiff("technicalDebt", null, FIVE_DAYS_IN_MINUTES).setCreationDate(null), - new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, null).setCreationDate(fourDaysAgo), - new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo) - ) - ); - when(issuable.issues()).thenReturn(newArrayList(issue)); - - decorator.decorate(resource, context); - - // remember : period1 is 5daysAgo, period2 is 10daysAgo - verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 4.0 * ONE_DAY_IN_MINUTES, 5.0 * ONE_DAY_IN_MINUTES))); - } - - @Test - public void save_on_one_issue_with_changelog_and_periods_have_no_dates() { - when(timeMachineConfiguration.periods()).thenReturn(newArrayList(new Period(1, null), new Period(2, null))); - - Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges( - newArrayList( - new FieldDiffs().setDiff("technicalDebt", null, FIVE_DAYS_IN_MINUTES).setCreationDate(null), - new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, null).setCreationDate(fourDaysAgo), - new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo) - ) - ); - when(issuable.issues()).thenReturn(newArrayList(issue)); - - decorator.decorate(resource, context); - - // remember : period1 is 5daysAgo, period2 is 10daysAgo - verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 5.0 * ONE_DAY_IN_MINUTES, 5.0 * ONE_DAY_IN_MINUTES))); - } - - @Test - public void save_on_one_issue_with_changelog_having_not_only_technical_debt_changes() { - Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges( - newArrayList( - new FieldDiffs() - .setDiff("actionPlan", "1.0", "1.1").setCreationDate(fourDaysAgo) - .setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(fourDaysAgo) - ) - ); - when(issuable.issues()).thenReturn(newArrayList(issue)); - - decorator.decorate(resource, context); - - // remember : period1 is 5daysAgo, period2 is 10daysAgo - verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 4.0 * ONE_DAY_IN_MINUTES, 4.0 * ONE_DAY_IN_MINUTES))); - } - - @Test - public void save_on_issues_with_changelog() { - Issue issue1 = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges( - newArrayList( - new FieldDiffs().setDiff("technicalDebt", TWO_DAYS_IN_MINUTES, FIVE_DAYS_IN_MINUTES).setCreationDate(rightNow), - new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(fourDaysAgo), - new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo) - ) - ); - Issue issue2 = new DefaultIssue().setKey("B").setCreationDate(tenDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES)).setChanges( - newArrayList( - new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(rightNow), - new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo) - ) - ); - when(issuable.issues()).thenReturn(newArrayList(issue1, issue2)); - - decorator.decorate(resource, context); - - // remember : period1 is 5daysAgo, period2 is 10daysAgo - verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 5.0 * ONE_DAY_IN_MINUTES, 7.0 * ONE_DAY_IN_MINUTES))); - } - - @Test - public void save_on_one_issue_without_changelog() { - when(issuable.issues()).thenReturn(newArrayList( - (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES))) - ); - - decorator.decorate(resource, context); - - // remember : period1 is 5daysAgo, period2 is 10daysAgo - verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 5.0 * ONE_DAY_IN_MINUTES))); - } - - @Test - public void save_on_one_issue_without_technical_debt_and_without_changelog() { - when(issuable.issues()).thenReturn(newArrayList( - (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(null)) - ); - - decorator.decorate(resource, context); - - // remember : period1 is 5daysAgo, period2 is 10daysAgo - verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 0.0))); - } - - @Test - public void save_on_one_issue_without_changelog_and_periods_have_no_dates() { - when(timeMachineConfiguration.periods()).thenReturn(newArrayList(new Period(1, null), new Period(2, null))); - - when(issuable.issues()).thenReturn(newArrayList( - (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES))) - ); - - decorator.decorate(resource, context); - - // remember : period1 is null, period2 is null - verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 5.0 * ONE_DAY_IN_MINUTES, 5.0 * ONE_DAY_IN_MINUTES))); - } - - @Test - public void save_on_issues_without_changelog() { - when(issuable.issues()).thenReturn(newArrayList( - (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)), - new DefaultIssue().setKey("B").setCreationDate(fiveDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES)) - )); - - decorator.decorate(resource, context); - - // remember : period1 is 5daysAgo, period2 is 10daysAgo - verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 7.0 * ONE_DAY_IN_MINUTES))); - } - - @Test - public void save_on_issues_with_changelog_and_issues_without_changelog() { - // issue1 and issue2 have changelog - Issue issue1 = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges( - newArrayList( - new FieldDiffs().setDiff("technicalDebt", TWO_DAYS_IN_MINUTES, FIVE_DAYS_IN_MINUTES).setCreationDate(rightNow), - new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(fourDaysAgo), - new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo) - ) - ); - Issue issue2 = new DefaultIssue().setKey("B").setCreationDate(tenDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES)).setChanges( - newArrayList( - new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(rightNow), - new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo) - ) - ); - - // issue3 and issue4 have no changelog - Issue issue3 = new DefaultIssue().setKey("C").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)); - Issue issue4 = new DefaultIssue().setKey("D").setCreationDate(fiveDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES)); - when(issuable.issues()).thenReturn(newArrayList(issue1, issue2, issue3, issue4)); - - decorator.decorate(resource, context); - - // remember : period1 is 5daysAgo, period2 is 10daysAgo - verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 5.0 * ONE_DAY_IN_MINUTES, 14.0 * ONE_DAY_IN_MINUTES))); - } - - @Test - public void not_save_if_measure_already_computed() { - when(context.getMeasure(CoreMetrics.NEW_TECHNICAL_DEBT)).thenReturn(new Measure()); - when(issuable.issues()).thenReturn(newArrayList( - (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)), - new DefaultIssue().setKey("B").setCreationDate(fiveDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES)) - )); - - decorator.decorate(resource, context); - - verify(context, never()).saveMeasure(argThat(new IsMeasure(CoreMetrics.NEW_TECHNICAL_DEBT))); - } - - /** - * SONAR-5059 - */ - @Test - public void not_return_negative_debt() { - Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(ONE_DAY_IN_MINUTES)).setChanges( - newArrayList( - // changelog created at is null because it has just been created on the current analysis - new FieldDiffs().setDiff("technicalDebt", TWO_DAYS_IN_MINUTES, ONE_DAY_IN_MINUTES).setCreationDate(null) - ) - ); - when(issuable.issues()).thenReturn(newArrayList(issue)); - - decorator.decorate(resource, context); - - // remember : period1 is 5daysAgo, period2 is 10daysAgo - verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 0.0))); - } - - - class IsVariationMeasure extends ArgumentMatcher { - Metric metric = null; - Double var1 = null; - Double var2 = null; - - public IsVariationMeasure(Metric metric, Double var1, Double var2) { - this.metric = metric; - this.var1 = var1; - this.var2 = var2; - } - - public boolean matches(Object o) { - if (!(o instanceof Measure)) { - return false; - } - Measure m = (Measure) o; - return ObjectUtils.equals(metric, m.getMetric()) && - ObjectUtils.equals(var1, m.getVariation1()) && - ObjectUtils.equals(var2, m.getVariation2()); - } - } - -} diff --git a/plugins/sonar-maven-batch-plugin/pom.xml b/plugins/sonar-maven-batch-plugin/pom.xml deleted file mode 100644 index 20af0ad3112..00000000000 --- a/plugins/sonar-maven-batch-plugin/pom.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - 4.0.0 - - org.codehaus.sonar - sonar - 4.3-SNAPSHOT - ../.. - - - org.codehaus.sonar.plugins - sonar-maven-batch-plugin - sonar-plugin - SonarQube :: Maven Batch Plugin - - - 3.0 - - - - - com.google.code.findbugs - jsr305 - provided - - - org.codehaus.sonar - sonar-batch - ${project.version} - provided - - - org.codehaus.sonar - sonar-plugin-api - provided - - - org.apache.maven - maven-plugin-api - ${maven.version} - provided - - - org.apache.maven - maven-core - ${maven.version} - provided - - - - - org.codehaus.sonar - sonar-testing-harness - test - - - org.mockito - mockito-all - test - - - - - - - - org.codehaus.sonar - sonar-packaging-maven-plugin - - Maven Batch Plugin - org.sonar.plugins.maven.MavenBatchPlugin - - - - - diff --git a/plugins/sonar-maven-batch-plugin/src/main/java/org/sonar/plugins/maven/DefaultMavenPluginExecutor.java b/plugins/sonar-maven-batch-plugin/src/main/java/org/sonar/plugins/maven/DefaultMavenPluginExecutor.java deleted file mode 100644 index fc5eef968b4..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/main/java/org/sonar/plugins/maven/DefaultMavenPluginExecutor.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * 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.plugins.maven; - -import org.apache.maven.execution.MavenSession; -import org.apache.maven.execution.ReactorManager; -import org.apache.maven.lifecycle.LifecycleExecutor; -import org.apache.maven.project.MavenProject; -import org.sonar.api.batch.SupportedEnvironment; -import org.sonar.api.batch.maven.MavenPlugin; -import org.sonar.api.batch.maven.MavenPluginHandler; -import org.sonar.api.resources.Project; -import org.sonar.api.utils.SonarException; -import org.sonar.api.utils.TimeProfiler; -import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem; -import org.sonar.batch.scan.maven.MavenPluginExecutor; - -import javax.annotation.Nullable; - -import java.lang.reflect.Method; -import java.util.Arrays; - -@SupportedEnvironment("maven") -public class DefaultMavenPluginExecutor implements MavenPluginExecutor { - - private final MavenProjectConverter pomConverter; - private LifecycleExecutor lifecycleExecutor; - private MavenSession mavenSession; - - public DefaultMavenPluginExecutor(LifecycleExecutor le, MavenSession mavenSession, MavenProjectConverter pomConverter) { - this.lifecycleExecutor = le; - this.mavenSession = mavenSession; - this.pomConverter = pomConverter; - } - - @Override - public final MavenPluginHandler execute(Project project, DefaultModuleFileSystem fs, MavenPluginHandler handler) { - for (String goal : handler.getGoals()) { - if (goal == null) { - throw new IllegalStateException("Maven goal can't be null"); - } - MavenPlugin plugin = MavenPlugin.getPlugin(project.getPom(), handler.getGroupId(), handler.getArtifactId()); - execute(project, - fs, - getGoal(handler.getGroupId(), handler.getArtifactId(), plugin != null && plugin.getPlugin() != null ? plugin.getPlugin().getVersion() : null, goal)); - } - return handler; - } - - @Override - public final void execute(Project project, DefaultModuleFileSystem fs, String goal) { - if (project.getPom() != null) { - TimeProfiler profiler = new TimeProfiler().start("Execute " + goal); - ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); - try { - concreteExecute(project.getPom(), goal); - } catch (Exception e) { - throw new SonarException("Unable to execute maven plugin", e); - } finally { - // Reset original ClassLoader that may have been changed during Maven Execution (see SONAR-1800) - Thread.currentThread().setContextClassLoader(currentClassLoader); - profiler.stop(); - } - if (!fs.isInitialized()) { - pomConverter.synchronizeFileSystem(project.getPom(), fs); - } - } - } - - static String getGoal(String groupId, String artifactId, @Nullable String version, String goal) { - String defaultVersion = version == null ? "" : version; - return new StringBuilder() - .append(groupId).append(":") - .append(artifactId).append(":") - .append(defaultVersion) - .append(":") - .append(goal) - .toString(); - } - - public void concreteExecute(MavenProject pom, String goal) { - Method executeMethod = null; - for (Method m : lifecycleExecutor.getClass().getMethods()) { - if ("execute".equals(m.getName())) { - executeMethod = m; - break; - } - } - if (executeMethod == null) { - throw new SonarException("Unable to find execute method on Maven LifecycleExecutor. Please check your Maven version."); - } - if (executeMethod.getParameterTypes().length == 1) { - concreteExecuteMaven3(pom, goal); - } else if (executeMethod.getParameterTypes().length == 3) { - concreteExecuteMaven2(executeMethod, pom, goal); - } else { - throw new SonarException("Unexpected parameter count on Maven LifecycleExecutor#execute method. Please check your Maven version."); - } - } - - public void concreteExecuteMaven3(MavenProject pom, String goal) { - MavenSession projectSession = mavenSession.clone(); - projectSession.setCurrentProject(pom); - projectSession.setProjects(Arrays.asList(pom)); - projectSession.getRequest().setRecursive(false); - projectSession.getRequest().setPom(pom.getFile()); - projectSession.getRequest().setGoals(Arrays.asList(goal)); - projectSession.getRequest().setInteractiveMode(false); - lifecycleExecutor.execute(projectSession); - if (projectSession.getResult().hasExceptions()) { - throw new SonarException("Exception during execution of " + goal); - } - } - - public void concreteExecuteMaven2(Method executeMethod, MavenProject pom, String goal) { - try { - ReactorManager reactor = new ReactorManager(Arrays.asList(pom)); - MavenSession clonedSession = new MavenSession(mavenSession.getContainer(), - mavenSession.getSettings(), - mavenSession.getLocalRepository(), - mavenSession.getEventDispatcher(), - reactor, - Arrays.asList(goal), - mavenSession.getExecutionRootDirectory(), - mavenSession.getExecutionProperties(), - mavenSession.getStartTime()); - executeMethod.invoke(lifecycleExecutor, clonedSession, reactor, clonedSession.getEventDispatcher()); - } catch (Exception e) { - throw new SonarException("Unable to execute Maven 2 plugin", e); - } - } - -} diff --git a/plugins/sonar-maven-batch-plugin/src/main/java/org/sonar/plugins/maven/MavenBatchPlugin.java b/plugins/sonar-maven-batch-plugin/src/main/java/org/sonar/plugins/maven/MavenBatchPlugin.java deleted file mode 100644 index 64375e5f696..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/main/java/org/sonar/plugins/maven/MavenBatchPlugin.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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.plugins.maven; - -import com.google.common.collect.ImmutableList; -import org.sonar.api.SonarPlugin; - -import java.util.List; - -public final class MavenBatchPlugin extends SonarPlugin { - - public List getExtensions() { - return ImmutableList.of(MavenProjectBootstrapper.class, DefaultMavenPluginExecutor.class, MavenProjectConverter.class, MavenProjectBuilder.class); - } -} diff --git a/plugins/sonar-maven-batch-plugin/src/main/java/org/sonar/plugins/maven/MavenProjectBootstrapper.java b/plugins/sonar-maven-batch-plugin/src/main/java/org/sonar/plugins/maven/MavenProjectBootstrapper.java deleted file mode 100644 index 1bb3ea37d21..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/main/java/org/sonar/plugins/maven/MavenProjectBootstrapper.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.plugins.maven; - -import org.apache.maven.execution.MavenSession; -import org.apache.maven.project.MavenProject; -import org.sonar.api.batch.SupportedEnvironment; -import org.sonar.api.batch.bootstrap.ProjectBootstrapper; -import org.sonar.api.batch.bootstrap.ProjectReactor; - -import java.util.List; - -/** - * @deprecated since 4.3 kept only to support old version of SonarQube Mojo - */ -@Deprecated -@SupportedEnvironment("maven") -public class MavenProjectBootstrapper implements ProjectBootstrapper { - - private MavenSession session; - private MavenProjectConverter mavenProjectConverter; - - public MavenProjectBootstrapper(MavenSession session, MavenProjectConverter mavenProjectConverter) { - this.session = session; - this.mavenProjectConverter = mavenProjectConverter; - } - - @Override - public ProjectReactor bootstrap() { - // Don't use session.getTopLevelProject or session.getProjects to keep compatibility with Maven 2 - List sortedProjects = session.getSortedProjects(); - MavenProject topLevelProject = null; - for (MavenProject project : sortedProjects) { - if (project.isExecutionRoot()) { - topLevelProject = project; - break; - } - } - if (topLevelProject == null) { - throw new IllegalStateException("Maven session does not declare a top level project"); - } - return new ProjectReactor(mavenProjectConverter.configure(sortedProjects, topLevelProject)); - } - -} diff --git a/plugins/sonar-maven-batch-plugin/src/main/java/org/sonar/plugins/maven/MavenProjectBuilder.java b/plugins/sonar-maven-batch-plugin/src/main/java/org/sonar/plugins/maven/MavenProjectBuilder.java deleted file mode 100644 index 3d9ebb12c2f..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/main/java/org/sonar/plugins/maven/MavenProjectBuilder.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.plugins.maven; - -import org.apache.maven.execution.MavenSession; -import org.apache.maven.project.MavenProject; -import org.sonar.api.batch.SupportedEnvironment; -import org.sonar.api.batch.bootstrap.ProjectBuilder; -import org.sonar.api.batch.bootstrap.ProjectDefinition; -import org.sonar.api.batch.bootstrap.ProjectReactor; - -import java.util.List; - -/** - * Class that inject MavenProject in each module container - */ -@SupportedEnvironment("maven") -public class MavenProjectBuilder extends ProjectBuilder { - - private final MavenSession mavenSession; - - public MavenProjectBuilder(MavenSession mavenSession) { - this.mavenSession = mavenSession; - } - - @Override - public void build(Context context) { - ProjectReactor reactor = context.projectReactor(); - for (ProjectDefinition moduleDef : reactor.getProjects()) { - setMavenProjectIfApplicable(moduleDef); - } - } - - private void setMavenProjectIfApplicable(ProjectDefinition definition) { - if (mavenSession != null) { - String moduleKey = definition.getKey(); - for (MavenProject mavenModule : (List) mavenSession.getSortedProjects()) { - String mavenModuleKey = mavenModule.getGroupId() + ":" + mavenModule.getArtifactId(); - if (mavenModuleKey.equals(moduleKey) && !definition.getContainerExtensions().contains(mavenModule)) { - definition.addContainerExtension(mavenModule); - } - } - } - } - -} diff --git a/plugins/sonar-maven-batch-plugin/src/main/java/org/sonar/plugins/maven/MavenProjectConverter.java b/plugins/sonar-maven-batch-plugin/src/main/java/org/sonar/plugins/maven/MavenProjectConverter.java deleted file mode 100644 index 3162b095361..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/main/java/org/sonar/plugins/maven/MavenProjectConverter.java +++ /dev/null @@ -1,311 +0,0 @@ -/* - * 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.plugins.maven; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.collect.Collections2; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import org.apache.commons.lang.StringUtils; -import org.apache.maven.model.CiManagement; -import org.apache.maven.model.IssueManagement; -import org.apache.maven.model.Scm; -import org.apache.maven.project.MavenProject; -import org.sonar.api.CoreProperties; -import org.sonar.api.batch.SupportedEnvironment; -import org.sonar.api.batch.bootstrap.ProjectDefinition; -import org.sonar.api.batch.maven.MavenUtils; -import org.sonar.api.task.TaskExtension; -import org.sonar.api.utils.MessageException; -import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem; -import org.sonar.java.api.JavaUtils; - -import javax.annotation.Nullable; - -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -/** - * @deprecated since 4.3 kept only to support old version of SonarQube Mojo - */ -@Deprecated -@SupportedEnvironment("maven") -public class MavenProjectConverter implements TaskExtension { - - private static final String UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE = "Unable to determine structure of project." + - " Probably you use Maven Advanced Reactor Options, which is not supported by SonarQube and should not be used."; - - public ProjectDefinition configure(List poms, MavenProject root) { - // projects by canonical path to pom.xml - Map paths = Maps.newHashMap(); - Map defs = Maps.newHashMap(); - - try { - configureModules(poms, paths, defs); - - rebuildModuleHierarchy(paths, defs); - } catch (IOException e) { - throw new IllegalStateException("Cannot configure project", e); - } - - ProjectDefinition rootProject = defs.get(root); - if (rootProject == null) { - throw new IllegalStateException(UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE); - } - return rootProject; - } - - private void rebuildModuleHierarchy(Map paths, Map defs) throws IOException { - for (Map.Entry entry : paths.entrySet()) { - MavenProject pom = entry.getValue(); - for (Object m : pom.getModules()) { - String moduleId = (String) m; - File modulePath = new File(pom.getBasedir(), moduleId); - MavenProject module = findMavenProject(modulePath, paths); - - ProjectDefinition parentProject = defs.get(pom); - if (parentProject == null) { - throw new IllegalStateException(UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE); - } - ProjectDefinition subProject = defs.get(module); - if (subProject == null) { - throw new IllegalStateException(UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE); - } - parentProject.addSubProject(subProject); - } - } - } - - private void configureModules(List poms, Map paths, Map defs) throws IOException { - for (MavenProject pom : poms) { - paths.put(pom.getFile().getCanonicalPath(), pom); - ProjectDefinition def = ProjectDefinition.create(); - merge(pom, def); - defs.put(pom, def); - } - } - - private static MavenProject findMavenProject(final File modulePath, Map paths) throws IOException { - if (modulePath.exists() && modulePath.isDirectory()) { - for (Map.Entry entry : paths.entrySet()) { - String pomFileParentDir = new File(entry.getKey()).getParent(); - if (pomFileParentDir.equals(modulePath.getCanonicalPath())) { - return entry.getValue(); - } - } - return null; - } - return paths.get(modulePath.getCanonicalPath()); - } - - @VisibleForTesting - void merge(MavenProject pom, ProjectDefinition definition) { - String key = getSonarKey(pom); - // IMPORTANT NOTE : reference on properties from POM model must not be saved, - // instead they should be copied explicitly - see SONAR-2896 - definition - .setProperties(pom.getModel().getProperties()) - .setKey(key) - .setVersion(pom.getVersion()) - .setName(pom.getName()) - .setDescription(pom.getDescription()) - .addContainerExtension(pom); - guessJavaVersion(pom, definition); - guessEncoding(pom, definition); - convertMavenLinksToProperties(definition, pom); - synchronizeFileSystem(pom, definition); - } - - private static String getSonarKey(MavenProject pom) { - return new StringBuilder().append(pom.getGroupId()).append(":").append(pom.getArtifactId()).toString(); - } - - private static void guessEncoding(MavenProject pom, ProjectDefinition definition) { - // See http://jira.codehaus.org/browse/SONAR-2151 - String encoding = MavenUtils.getSourceEncoding(pom); - if (encoding != null) { - definition.setProperty(CoreProperties.ENCODING_PROPERTY, encoding); - } - } - - private static void guessJavaVersion(MavenProject pom, ProjectDefinition definition) { - // See http://jira.codehaus.org/browse/SONAR-2148 - // Get Java source and target versions from maven-compiler-plugin. - String version = MavenUtils.getJavaSourceVersion(pom); - if (version != null) { - definition.setProperty(JavaUtils.JAVA_SOURCE_PROPERTY, version); - } - version = MavenUtils.getJavaVersion(pom); - if (version != null) { - definition.setProperty(JavaUtils.JAVA_TARGET_PROPERTY, version); - } - } - - /** - * For SONAR-3676 - */ - private static void convertMavenLinksToProperties(ProjectDefinition definition, MavenProject pom) { - setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_HOME_PAGE, pom.getUrl()); - - Scm scm = pom.getScm(); - if (scm == null) { - scm = new Scm(); - } - setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_SOURCES, scm.getUrl()); - setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_SOURCES_DEV, scm.getDeveloperConnection()); - - CiManagement ci = pom.getCiManagement(); - if (ci == null) { - ci = new CiManagement(); - } - setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_CI, ci.getUrl()); - - IssueManagement issues = pom.getIssueManagement(); - if (issues == null) { - issues = new IssueManagement(); - } - setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_ISSUE_TRACKER, issues.getUrl()); - } - - private static void setPropertyIfNotAlreadyExists(ProjectDefinition definition, String propertyKey, String propertyValue) { - if (StringUtils.isBlank(definition.getProperties().getProperty(propertyKey))) { - definition.setProperty(propertyKey, StringUtils.defaultString(propertyValue)); - } - } - - public void synchronizeFileSystem(MavenProject pom, ProjectDefinition into) { - into.setBaseDir(pom.getBasedir()); - File buildDir = getBuildDir(pom); - if (buildDir != null) { - into.setBuildDir(buildDir); - into.setWorkDir(getSonarWorkDir(pom)); - } - into.setSourceDirs(toPaths(mainDirs(pom))); - into.setTestDirs(toPaths(testDirs(pom))); - File binaryDir = resolvePath(pom.getBuild().getOutputDirectory(), pom.getBasedir()); - if (binaryDir != null) { - into.addBinaryDir(binaryDir); - } - } - - public static File getSonarWorkDir(MavenProject pom) { - return new File(getBuildDir(pom), "sonar"); - } - - private static File getBuildDir(MavenProject pom) { - return resolvePath(pom.getBuild().getDirectory(), pom.getBasedir()); - } - - public void synchronizeFileSystem(MavenProject pom, DefaultModuleFileSystem into) { - into.resetDirs( - pom.getBasedir(), - getBuildDir(pom), - mainDirs(pom), - testDirs(pom), - Arrays.asList(resolvePath(pom.getBuild().getOutputDirectory(), pom.getBasedir()))); - } - - static File resolvePath(@Nullable String path, File basedir) { - if (path != null) { - File file = new File(StringUtils.trim(path)); - if (!file.isAbsolute()) { - try { - file = new File(basedir, path).getCanonicalFile(); - } catch (IOException e) { - throw new IllegalStateException("Unable to resolve path '" + path + "'", e); - } - } - return file; - } - return null; - } - - static List resolvePaths(List paths, File basedir) { - List result = Lists.newArrayList(); - for (String path : paths) { - File dir = resolvePath(path, basedir); - if (dir != null) { - result.add(dir); - } - } - return result; - } - - private List mainDirs(MavenProject pom) { - return sourceDirs(pom, ProjectDefinition.SOURCE_DIRS_PROPERTY, pom.getCompileSourceRoots()); - } - - private List testDirs(MavenProject pom) { - return sourceDirs(pom, ProjectDefinition.TEST_DIRS_PROPERTY, pom.getTestCompileSourceRoots()); - } - - private List sourceDirs(MavenProject pom, String propertyKey, List mavenDirs) { - List paths; - String prop = pom.getProperties().getProperty(propertyKey); - if (prop != null) { - paths = Arrays.asList(StringUtils.split(prop, ",")); - // do not remove dirs that do not exist. They must be kept in order to - // notify users that value of sonar.sources has a typo. - return existingDirsOrFail(resolvePaths(paths, pom.getBasedir()), pom, propertyKey); - } - - List dirs = resolvePaths(mavenDirs, pom.getBasedir()); - - // Maven provides some directories that do not exist. They - // should be removed - return keepExistingDirs(dirs); - } - - private List existingDirsOrFail(List dirs, MavenProject pom, String propertyKey) { - for (File dir : dirs) { - if (!dir.isDirectory() || !dir.exists()) { - throw MessageException.of(String.format( - "The directory '%s' does not exist for Maven module %s. Please check the property %s", - dir.getAbsolutePath(), pom.getId(), propertyKey)); - } - } - return dirs; - } - - private static List keepExistingDirs(List files) { - return Lists.newArrayList(Collections2.filter(files, new Predicate() { - @Override - public boolean apply(File dir) { - return dir != null && dir.exists() && dir.isDirectory(); - } - })); - } - - private static String[] toPaths(Collection dirs) { - Collection paths = Collections2.transform(dirs, new Function() { - @Override - public String apply(File dir) { - return dir.getAbsolutePath(); - } - }); - return paths.toArray(new String[paths.size()]); - } -} diff --git a/plugins/sonar-maven-batch-plugin/src/main/java/org/sonar/plugins/maven/package-info.java b/plugins/sonar-maven-batch-plugin/src/main/java/org/sonar/plugins/maven/package-info.java deleted file mode 100644 index 4e7cfd86137..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/main/java/org/sonar/plugins/maven/package-info.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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. - */ - -/** - * This package is a part of bootstrap process, so we should take care about backward compatibility. - */ -@ParametersAreNonnullByDefault -package org.sonar.plugins.maven; - -import javax.annotation.ParametersAreNonnullByDefault; - diff --git a/plugins/sonar-maven-batch-plugin/src/test/java/org/sonar/plugins/maven/DefaultMavenPluginExecutorTest.java b/plugins/sonar-maven-batch-plugin/src/test/java/org/sonar/plugins/maven/DefaultMavenPluginExecutorTest.java deleted file mode 100644 index fcaa0bf34b5..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/test/java/org/sonar/plugins/maven/DefaultMavenPluginExecutorTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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.plugins.maven; - -import org.apache.maven.project.MavenProject; -import org.junit.Test; -import org.sonar.api.batch.maven.MavenPlugin; -import org.sonar.api.batch.maven.MavenPluginHandler; -import org.sonar.api.resources.Project; -import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem; - -import java.io.File; - -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyList; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -public class DefaultMavenPluginExecutorTest { - - @Test - public void plugin_version_should_be_optional() { - assertThat(DefaultMavenPluginExecutor.getGoal("group", "artifact", null, "goal"), is("group:artifact::goal")); - } - - @Test - public void test_plugin_version() { - assertThat(DefaultMavenPluginExecutor.getGoal("group", "artifact", "3.54", "goal"), is("group:artifact:3.54:goal")); - } - - @Test - public void should_ignore_non_maven_projects() { - DefaultMavenPluginExecutor executor = new DefaultMavenPluginExecutor(null, null, mock(MavenProjectConverter.class)) { - @Override - public void concreteExecute(MavenProject pom, String goal) { - pom.addCompileSourceRoot("src/java"); - } - }; - Project foo = new Project("foo"); - DefaultModuleFileSystem fs = mock(DefaultModuleFileSystem.class); - executor.execute(foo, fs, new AddSourceMavenPluginHandler()); - - verify(fs, never()).resetDirs(any(File.class), any(File.class), anyList(), anyList(), anyList()); - } - - static class AddSourceMavenPluginHandler implements MavenPluginHandler { - public String getGroupId() { - return "fake"; - } - - public String getArtifactId() { - return "fake"; - } - - public String getVersion() { - return "2.2"; - } - - public boolean isFixedVersion() { - return false; - } - - public String[] getGoals() { - return new String[]{"fake"}; - } - - public void configure(Project project, MavenPlugin plugin) { - } - } - -} diff --git a/plugins/sonar-maven-batch-plugin/src/test/java/org/sonar/plugins/maven/MavenBatchPluginTest.java b/plugins/sonar-maven-batch-plugin/src/test/java/org/sonar/plugins/maven/MavenBatchPluginTest.java deleted file mode 100644 index 0504cb99f3f..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/test/java/org/sonar/plugins/maven/MavenBatchPluginTest.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.plugins.maven; - -import org.junit.Test; - -import static org.fest.assertions.Assertions.assertThat; - -public class MavenBatchPluginTest { - - @Test - public void testGetExtensions() { - MavenBatchPlugin plugin = new MavenBatchPlugin(); - assertThat(plugin.getExtensions()).hasSize(4); - } - -} diff --git a/plugins/sonar-maven-batch-plugin/src/test/java/org/sonar/plugins/maven/MavenProjectBootstrapperTest.java b/plugins/sonar-maven-batch-plugin/src/test/java/org/sonar/plugins/maven/MavenProjectBootstrapperTest.java deleted file mode 100644 index 9c7cb4ecde8..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/test/java/org/sonar/plugins/maven/MavenProjectBootstrapperTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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.plugins.maven; - -import org.apache.maven.execution.MavenSession; -import org.apache.maven.project.MavenProject; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.batch.bootstrap.ProjectDefinition; -import org.sonar.api.batch.bootstrap.ProjectReactor; - -import java.util.Arrays; -import java.util.List; - -import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class MavenProjectBootstrapperTest { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Test - public void bootstrap() throws Exception { - ProjectDefinition def = mock(ProjectDefinition.class); - MavenSession session = mock(MavenSession.class); - MavenProject rootProject = new MavenProject(); - rootProject.setExecutionRoot(true); - List projects = Arrays.asList(rootProject); - when(session.getSortedProjects()).thenReturn(projects); - - MavenProjectConverter pomConverter = mock(MavenProjectConverter.class); - when(pomConverter.configure(projects, rootProject)).thenReturn(def); - MavenProjectBootstrapper bootstrapper = new MavenProjectBootstrapper(session, pomConverter); - - ProjectReactor reactor = bootstrapper.bootstrap(); - - assertThat(reactor).isNotNull(); - verify(pomConverter).configure(projects, rootProject); - } - - @Test - public void should_fail_if_no_top_level_project() throws Exception { - thrown.expect(IllegalStateException.class); - thrown.expectMessage("Maven session does not declare a top level project"); - - MavenSession session = mock(MavenSession.class); - MavenProjectConverter pomConverter = new MavenProjectConverter(); - MavenProjectBootstrapper bootstrapper = new MavenProjectBootstrapper(session, pomConverter); - - bootstrapper.bootstrap(); - } -} diff --git a/plugins/sonar-maven-batch-plugin/src/test/java/org/sonar/plugins/maven/MavenProjectConverterTest.java b/plugins/sonar-maven-batch-plugin/src/test/java/org/sonar/plugins/maven/MavenProjectConverterTest.java deleted file mode 100644 index 6cc799f029c..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/test/java/org/sonar/plugins/maven/MavenProjectConverterTest.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * 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.plugins.maven; - -import org.apache.commons.io.FileUtils; -import org.apache.maven.model.Model; -import org.apache.maven.model.io.xpp3.MavenXpp3Reader; -import org.apache.maven.project.MavenProject; -import org.codehaus.plexus.util.xml.pull.XmlPullParserException; -import org.hamcrest.core.Is; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.sonar.api.CoreProperties; -import org.sonar.api.batch.bootstrap.ProjectDefinition; -import org.sonar.test.TestUtils; - -import java.io.File; -import java.io.IOException; -import java.io.StringReader; -import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.Properties; - -import static org.fest.assertions.Assertions.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; - -public class MavenProjectConverterTest { - - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - - MavenProjectConverter converter = new MavenProjectConverter(); - - /** - * See SONAR-2681 - */ - @Test - public void shouldThrowExceptionWhenUnableToDetermineProjectStructure() { - MavenProject root = new MavenProject(); - root.setFile(new File("/foo/pom.xml")); - root.getBuild().setDirectory("target"); - root.getModules().add("module/pom.xml"); - - try { - converter.configure(Arrays.asList(root), root); - fail(); - } catch (IllegalStateException e) { - assertThat(e.getMessage(), containsString("Advanced Reactor Options")); - } - } - - @Test - public void shouldConvertModules() throws IOException { - File basedir = temp.newFolder(); - - MavenProject root = newMavenProject("com.foo", "parent", "1.0-SNAPSHOT"); - root.setFile(new File(basedir, "pom.xml")); - root.getBuild().setDirectory("target"); - root.getBuild().setOutputDirectory("target/classes"); - root.getModules().add("module/pom.xml"); - MavenProject module = newMavenProject("com.foo", "moduleA", "1.0-SNAPSHOT"); - module.setFile(new File(basedir, "module/pom.xml")); - module.getBuild().setDirectory("target"); - module.getBuild().setOutputDirectory("target/classes"); - ProjectDefinition project = converter.configure(Arrays.asList(root, module), root); - - assertThat(project.getSubProjects().size(), is(1)); - } - - private MavenProject newMavenProject(String groupId, String artifactId, String version) { - Model model = new Model(); - model.setGroupId(groupId); - model.setArtifactId(artifactId); - model.setVersion(version); - return new MavenProject(model); - } - - @Test - public void shouldConvertProperties() { - MavenProject pom = new MavenProject(); - pom.setGroupId("foo"); - pom.setArtifactId("bar"); - pom.setVersion("1.0.1"); - pom.setName("Test"); - pom.setDescription("just test"); - pom.setFile(new File("/foo/pom.xml")); - pom.getBuild().setDirectory("target"); - ProjectDefinition project = ProjectDefinition.create(); - converter.merge(pom, project); - - Properties properties = project.getProperties(); - assertThat(properties.getProperty(CoreProperties.PROJECT_KEY_PROPERTY), is("foo:bar")); - assertThat(properties.getProperty(CoreProperties.PROJECT_VERSION_PROPERTY), is("1.0.1")); - assertThat(properties.getProperty(CoreProperties.PROJECT_NAME_PROPERTY), is("Test")); - assertThat(properties.getProperty(CoreProperties.PROJECT_DESCRIPTION_PROPERTY), is("just test")); - } - - @Test - public void moduleNameShouldEqualArtifactId() throws Exception { - File rootDir = TestUtils.getResource("/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/"); - MavenProject parent = loadPom("/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/pom.xml", true); - MavenProject module1 = loadPom("/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module1/pom.xml", false); - MavenProject module2 = loadPom("/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module2/pom.xml", false); - - ProjectDefinition rootDef = converter.configure(Arrays.asList(parent, module1, module2), parent); - - assertThat(rootDef.getSubProjects().size(), Is.is(2)); - assertThat(rootDef.getKey(), Is.is("org.test:parent")); - assertNull(rootDef.getParent()); - assertThat(rootDef.getBaseDir(), is(rootDir)); - - ProjectDefinition module1Def = rootDef.getSubProjects().get(0); - assertThat(module1Def.getKey(), Is.is("org.test:module1")); - assertThat(module1Def.getParent(), Is.is(rootDef)); - assertThat(module1Def.getBaseDir(), Is.is(new File(rootDir, "module1"))); - assertThat(module1Def.getSubProjects().size(), Is.is(0)); - } - - @Test - public void moduleNameDifferentThanArtifactId() throws Exception { - File rootDir = TestUtils.getResource("/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/"); - MavenProject parent = loadPom("/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/pom.xml", true); - MavenProject module1 = loadPom("/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path1/pom.xml", false); - MavenProject module2 = loadPom("/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path2/pom.xml", false); - - ProjectDefinition rootDef = converter.configure(Arrays.asList(parent, module1, module2), parent); - - assertThat(rootDef.getSubProjects().size(), is(2)); - assertThat(rootDef.getKey(), is("org.test:parent")); - assertNull(rootDef.getParent()); - assertThat(rootDef.getBaseDir(), is(rootDir)); - - ProjectDefinition module1Def = rootDef.getSubProjects().get(0); - assertThat(module1Def.getKey(), Is.is("org.test:module1")); - assertThat(module1Def.getParent(), Is.is(rootDef)); - assertThat(module1Def.getBaseDir(), Is.is(new File(rootDir, "path1"))); - assertThat(module1Def.getSubProjects().size(), Is.is(0)); - } - - @Test - public void should_find_module_with_maven_project_file_naming_different_from_pom_xml() throws Exception { - File rootDir = TestUtils.getResource("/org/sonar/plugins/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/"); - MavenProject parent = loadPom("/org/sonar/plugins/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/pom.xml", true); - MavenProject module = loadPom("/org/sonar/plugins/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom_having_different_name.xml", false); - - ProjectDefinition rootDef = converter.configure(Arrays.asList(parent, module), parent); - - assertThat(rootDef.getSubProjects().size(), Is.is(1)); - assertThat(rootDef.getKey(), Is.is("org.test:parent")); - assertNull(rootDef.getParent()); - assertThat(rootDef.getBaseDir(), is(rootDir)); - - ProjectDefinition module1Def = rootDef.getSubProjects().get(0); - assertThat(module1Def.getKey(), Is.is("org.test:module")); - assertThat(module1Def.getParent(), Is.is(rootDef)); - assertThat(module1Def.getBaseDir(), Is.is(new File(rootDir, "module"))); - assertThat(module1Def.getSubProjects().size(), Is.is(0)); - } - - @Test - public void testSingleProjectWithoutModules() throws Exception { - File rootDir = TestUtils.getResource("/org/sonar/plugins/maven/MavenProjectConverterTest/singleProjectWithoutModules/"); - MavenProject pom = loadPom("/org/sonar/plugins/maven/MavenProjectConverterTest/singleProjectWithoutModules/pom.xml", true); - - ProjectDefinition rootDef = converter.configure(Arrays.asList(pom), pom); - - assertThat(rootDef.getKey(), is("org.test:parent")); - assertThat(rootDef.getSubProjects().size(), is(0)); - assertNull(rootDef.getParent()); - assertThat(rootDef.getBaseDir(), is(rootDir)); - } - - @Test - public void shouldConvertLinksToProperties() throws Exception { - MavenProject pom = loadPom("/org/sonar/plugins/maven/MavenProjectConverterTest/projectWithLinks/pom.xml", true); - - ProjectDefinition rootDef = converter.configure(Arrays.asList(pom), pom); - - Properties props = rootDef.getProperties(); - assertThat(props.getProperty(CoreProperties.LINKS_HOME_PAGE)).isEqualTo("http://home.com"); - assertThat(props.getProperty(CoreProperties.LINKS_CI)).isEqualTo("http://ci.com"); - assertThat(props.getProperty(CoreProperties.LINKS_ISSUE_TRACKER)).isEqualTo("http://issues.com"); - assertThat(props.getProperty(CoreProperties.LINKS_SOURCES)).isEqualTo("http://sources.com"); - assertThat(props.getProperty(CoreProperties.LINKS_SOURCES_DEV)).isEqualTo("http://sources-dev.com"); - } - - @Test - public void shouldNotConvertLinksToPropertiesIfPropertyAlreadyDefined() throws Exception { - MavenProject pom = loadPom("/org/sonar/plugins/maven/MavenProjectConverterTest/projectWithLinksAndProperties/pom.xml", true); - - ProjectDefinition rootDef = converter.configure(Arrays.asList(pom), pom); - - Properties props = rootDef.getProperties(); - - // Those properties have been fed by the POM elements , , ... - assertThat(props.getProperty(CoreProperties.LINKS_CI)).isEqualTo("http://ci.com"); - assertThat(props.getProperty(CoreProperties.LINKS_ISSUE_TRACKER)).isEqualTo("http://issues.com"); - assertThat(props.getProperty(CoreProperties.LINKS_SOURCES_DEV)).isEqualTo("http://sources-dev.com"); - - // ... but those ones have been overridden by in the POM - assertThat(props.getProperty(CoreProperties.LINKS_SOURCES)).isEqualTo("http://sources.com-OVERRIDEN-BY-PROPS"); - assertThat(props.getProperty(CoreProperties.LINKS_HOME_PAGE)).isEqualTo("http://home.com-OVERRIDEN-BY-PROPS"); - } - - @Test - public void shouldLoadSourceEncoding() throws Exception { - MavenProject pom = loadPom("/org/sonar/plugins/maven/MavenProjectConverterTest/sourceEncoding/pom.xml", true); - - ProjectDefinition rootDef = converter.configure(Arrays.asList(pom), pom); - - assertThat(rootDef.getProperties().getProperty(CoreProperties.ENCODING_PROPERTY)).isEqualTo("Shift_JIS"); - } - - private MavenProject loadPom(String pomPath, boolean isRoot) throws URISyntaxException, IOException, XmlPullParserException { - File pomFile = new File(getClass().getResource(pomPath).toURI()); - Model model = new MavenXpp3Reader().read(new StringReader(FileUtils.readFileToString(pomFile))); - MavenProject pom = new MavenProject(model); - pom.setFile(pomFile); - pom.getBuild().setDirectory("target"); - pom.setExecutionRoot(isRoot); - return pom; - } -} diff --git a/plugins/sonar-maven-batch-plugin/src/test/java/org/sonar/plugins/maven/SonarMavenProjectBuilderTest.java b/plugins/sonar-maven-batch-plugin/src/test/java/org/sonar/plugins/maven/SonarMavenProjectBuilderTest.java deleted file mode 100644 index a39798c1c85..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/test/java/org/sonar/plugins/maven/SonarMavenProjectBuilderTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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.plugins.maven; - -import org.apache.maven.execution.MavenSession; -import org.apache.maven.project.MavenProject; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.sonar.api.batch.bootstrap.ProjectDefinition; - -import java.util.Arrays; -import java.util.List; - -import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class SonarMavenProjectBuilderTest { - - @Test - public void testSimpleProject() { - MavenSession session = mock(MavenSession.class); - MavenProject rootProject = mock(MavenProject.class); - when(rootProject.isExecutionRoot()).thenReturn(true); - when(session.getSortedProjects()).thenReturn(Arrays.asList(rootProject)); - - MavenProjectConverter mavenProjectConverter = mock(MavenProjectConverter.class); - ProjectDefinition projectDefinition = ProjectDefinition.create(); - when(mavenProjectConverter.configure(any(List.class), any(MavenProject.class))).thenReturn(projectDefinition); - MavenProjectBootstrapper builder = new MavenProjectBootstrapper(session, mavenProjectConverter); - - assertThat(builder.bootstrap().getRoot()).isEqualTo(projectDefinition); - - ArgumentCaptor argument = ArgumentCaptor.forClass(List.class); - verify(mavenProjectConverter).configure(argument.capture(), eq(rootProject)); - assertThat(argument.getValue()).contains(rootProject); - } - - @Test - public void testMultimoduleProject() { - MavenSession session = mock(MavenSession.class); - MavenProject rootProject = mock(MavenProject.class); - MavenProject module1 = mock(MavenProject.class); - MavenProject module2 = mock(MavenProject.class); - when(rootProject.isExecutionRoot()).thenReturn(true); - when(module1.isExecutionRoot()).thenReturn(false); - when(module2.isExecutionRoot()).thenReturn(false); - when(session.getSortedProjects()).thenReturn(Arrays.asList(module1, module2, rootProject)); - - MavenProjectConverter mavenProjectConverter = mock(MavenProjectConverter.class); - ProjectDefinition projectDefinition = ProjectDefinition.create(); - when(mavenProjectConverter.configure(any(List.class), any(MavenProject.class))).thenReturn(projectDefinition); - MavenProjectBootstrapper builder = new MavenProjectBootstrapper(session, mavenProjectConverter); - - assertThat(builder.bootstrap().getRoot()).isEqualTo(projectDefinition); - - ArgumentCaptor argument = ArgumentCaptor.forClass(List.class); - verify(mavenProjectConverter).configure(argument.capture(), eq(rootProject)); - assertThat(argument.getValue()).contains(module1, module2, rootProject); - } - -} diff --git a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom.xml b/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom.xml deleted file mode 100644 index 7b49562c2e0..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom.xml +++ /dev/null @@ -1,11 +0,0 @@ - - 4.0.0 - - org.test - parent - 0.1-SNAPSHOT - - module - jar - \ No newline at end of file diff --git a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom_having_different_name.xml b/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom_having_different_name.xml deleted file mode 100644 index 7b49562c2e0..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom_having_different_name.xml +++ /dev/null @@ -1,11 +0,0 @@ - - 4.0.0 - - org.test - parent - 0.1-SNAPSHOT - - module - jar - \ No newline at end of file diff --git a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/pom.xml b/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/pom.xml deleted file mode 100644 index 9d2244eb148..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/pom.xml +++ /dev/null @@ -1,11 +0,0 @@ - - 4.0.0 - org.test - parent - 0.1-SNAPSHOT - pom - - module - - \ No newline at end of file diff --git a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path1/pom.xml b/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path1/pom.xml deleted file mode 100644 index 470f2d1f6e5..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path1/pom.xml +++ /dev/null @@ -1,11 +0,0 @@ - - 4.0.0 - - org.test - parent - 0.1-SNAPSHOT - - module1 - jar - \ No newline at end of file diff --git a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path2/pom.xml b/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path2/pom.xml deleted file mode 100644 index 88101678e0f..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path2/pom.xml +++ /dev/null @@ -1,11 +0,0 @@ - - 4.0.0 - - org.test - parent - 0.1-SNAPSHOT - - module2 - jar - \ No newline at end of file diff --git a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/pom.xml b/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/pom.xml deleted file mode 100644 index afd92c0dbee..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/pom.xml +++ /dev/null @@ -1,12 +0,0 @@ - - 4.0.0 - org.test - parent - 0.1-SNAPSHOT - pom - - path1 - path2 - - \ No newline at end of file diff --git a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module1/pom.xml b/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module1/pom.xml deleted file mode 100644 index 470f2d1f6e5..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module1/pom.xml +++ /dev/null @@ -1,11 +0,0 @@ - - 4.0.0 - - org.test - parent - 0.1-SNAPSHOT - - module1 - jar - \ No newline at end of file diff --git a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module2/pom.xml b/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module2/pom.xml deleted file mode 100644 index 88101678e0f..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module2/pom.xml +++ /dev/null @@ -1,11 +0,0 @@ - - 4.0.0 - - org.test - parent - 0.1-SNAPSHOT - - module2 - jar - \ No newline at end of file diff --git a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/pom.xml b/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/pom.xml deleted file mode 100644 index cc73a43ec08..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/pom.xml +++ /dev/null @@ -1,12 +0,0 @@ - - 4.0.0 - org.test - parent - 0.1-SNAPSHOT - pom - - module1 - module2 - - \ No newline at end of file diff --git a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/projectWithLinks/pom.xml b/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/projectWithLinks/pom.xml deleted file mode 100644 index 460e8967e5c..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/projectWithLinks/pom.xml +++ /dev/null @@ -1,18 +0,0 @@ - - 4.0.0 - org.test - parent - 0.1-SNAPSHOT - pom - http://home.com - - http://ci.com - - - http://issues.com - - - http://sources.com - http://sources-dev.com - - \ No newline at end of file diff --git a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/projectWithLinksAndProperties/pom.xml b/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/projectWithLinksAndProperties/pom.xml deleted file mode 100644 index 5b024e5c4a7..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/projectWithLinksAndProperties/pom.xml +++ /dev/null @@ -1,27 +0,0 @@ - - 4.0.0 - org.test - parent - 0.1-SNAPSHOT - pom - http://home.com - - http://ci.com - - - http://issues.com - - - http://sources.com - http://sources-dev.com - - - - - - http://home.com-OVERRIDEN-BY-PROPS - http://sources.com-OVERRIDEN-BY-PROPS - - - - \ No newline at end of file diff --git a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/singleProjectWithoutModules/pom.xml b/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/singleProjectWithoutModules/pom.xml deleted file mode 100644 index ffd40530c5d..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/singleProjectWithoutModules/pom.xml +++ /dev/null @@ -1,8 +0,0 @@ - - 4.0.0 - org.test - parent - 0.1-SNAPSHOT - pom - \ No newline at end of file diff --git a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/sourceEncoding/pom.xml b/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/sourceEncoding/pom.xml deleted file mode 100644 index 01bf73cfa9e..00000000000 --- a/plugins/sonar-maven-batch-plugin/src/test/resources/org/sonar/plugins/maven/MavenProjectConverterTest/sourceEncoding/pom.xml +++ /dev/null @@ -1,11 +0,0 @@ - - 4.0.0 - org.test - parent - 0.1-SNAPSHOT - pom - http://home.com - - Shift_JIS - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index 62b3fcaea10..2d8029ed392 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,6 @@ plugins/sonar-cpd-plugin plugins/sonar-design-plugin plugins/sonar-email-notifications-plugin - plugins/sonar-maven-batch-plugin plugins/sonar-xoo-plugin diff --git a/sonar-application/assembly.xml b/sonar-application/assembly.xml index d99cf5fedb4..6bdb789c059 100644 --- a/sonar-application/assembly.xml +++ b/sonar-application/assembly.xml @@ -18,8 +18,16 @@ net.sourceforge.jtds:jtds org.codehaus.sonar.plugins:* org.codehaus.sonar-plugins.*:* + org.codehaus.sonar:sonar-batch-maven-compat + + lib/batch + false + + org.codehaus.sonar:sonar-batch-maven-compat + + lib/core-plugins diff --git a/sonar-application/pom.xml b/sonar-application/pom.xml index c59caf02c7b..beb6302868a 100644 --- a/sonar-application/pom.xml +++ b/sonar-application/pom.xml @@ -57,6 +57,12 @@ ch.qos.logback logback-core + + ${pom.groupId} + sonar-batch-maven-compat + ${pom.version} + runtime + org.apache.tomcat.embed tomcat-embed-core @@ -163,13 +169,6 @@ sonar-plugin runtime - - org.codehaus.sonar.plugins - sonar-maven-batch-plugin - ${project.version} - sonar-plugin - runtime - org.sonatype.jsw-binaries jsw-binaries @@ -258,8 +257,8 @@ - 55000000 - 75000000 + 80000000 + 88000000 ${project.build.directory}/sonarqube-${project.version}.zip diff --git a/sonar-batch-maven-compat/pom.xml b/sonar-batch-maven-compat/pom.xml index 3990762b480..087be6a8d57 100644 --- a/sonar-batch-maven-compat/pom.xml +++ b/sonar-batch-maven-compat/pom.xml @@ -13,11 +13,15 @@ Compatibility layer, which provides MavenProject for non-Maven environments. + + ${pom.groupId} + sonar-batch + ${pom.version} + org.apache.maven maven-project 2.0.7 - compile org.codehaus.plexus @@ -43,20 +47,4 @@ - - - - release - - - - maven-deploy-plugin - - true - - - - - - diff --git a/sonar-batch/pom.xml b/sonar-batch/pom.xml index 4d473f38149..caeffd62bcd 100644 --- a/sonar-batch/pom.xml +++ b/sonar-batch/pom.xml @@ -7,7 +7,6 @@ 4.3-SNAPSHOT - org.codehaus.sonar sonar-batch SonarQube :: Batch @@ -40,6 +39,16 @@ org.codehaus.sonar sonar-plugin-api + + + jfree + jcommon + + + jfree + jfreechat + + org.codehaus.sonar @@ -52,11 +61,13 @@ org.apache.maven maven-plugin-api + 3.0 provided org.apache.maven maven-core + 3.0 provided diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java new file mode 100644 index 00000000000..02c98554fa6 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java @@ -0,0 +1,47 @@ +/* + * 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.batch.bootstrap; + +import com.google.common.collect.Lists; +import org.sonar.batch.debt.DebtDecorator; +import org.sonar.batch.debt.IssueChangelogDebtCalculator; +import org.sonar.batch.debt.NewDebtDecorator; +import org.sonar.batch.maven.DefaultMavenPluginExecutor; +import org.sonar.batch.maven.MavenProjectBootstrapper; +import org.sonar.batch.maven.MavenProjectBuilder; +import org.sonar.batch.maven.MavenProjectConverter; +import org.sonar.core.config.CorePropertyDefinitions; + +import java.util.Collection; +import java.util.List; + +public class BatchComponents { + public static Collection all() { + List components = Lists.newArrayList( + // Maven + MavenProjectBootstrapper.class, DefaultMavenPluginExecutor.class, MavenProjectConverter.class, MavenProjectBuilder.class, + + // Debt + IssueChangelogDebtCalculator.class, DebtDecorator.class, NewDebtDecorator.class + ); + components.addAll(CorePropertyDefinitions.all()); + return components; + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java index 039cc1c210c..bb2b2e431f2 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java @@ -44,6 +44,13 @@ public class ExtensionInstaller { public ExtensionInstaller install(ComponentContainer container, ExtensionMatcher matcher) { boolean preview = analysisMode.isPreview(); + + // core components + for (Object o : BatchComponents.all()) { + doInstall(container, matcher, null, preview, o); + } + + // plugin extensions for (Map.Entry entry : pluginRepository.getPluginsByMetadata().entrySet()) { PluginMetadata metadata = entry.getKey(); Plugin plugin = entry.getValue(); diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java index c5992b6475a..d823a0650cf 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java @@ -24,6 +24,7 @@ import org.sonar.api.config.Settings; import org.sonar.api.platform.ComponentContainer; import org.sonar.api.resources.ResourceTypes; import org.sonar.api.task.Task; +import org.sonar.api.task.TaskComponent; import org.sonar.api.task.TaskDefinition; import org.sonar.api.task.TaskExtension; import org.sonar.api.utils.SonarException; @@ -55,7 +56,7 @@ public class TaskContainer extends ComponentContainer { private void installTaskExtensions() { getComponentByType(ExtensionInstaller.class).install(this, new ExtensionMatcher() { public boolean accept(Object extension) { - return ExtensionUtils.isType(extension, TaskExtension.class); + return ExtensionUtils.isType(extension, TaskComponent.class); } }); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java index 88de3031103..541a63a6bd7 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java +++ b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java @@ -20,24 +20,19 @@ package org.sonar.batch.components; import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; import org.apache.commons.configuration.Configuration; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.BatchExtension; import org.sonar.api.CoreProperties; -import org.sonar.api.config.PropertyDefinition; import org.sonar.api.config.Settings; import org.sonar.api.database.model.Snapshot; -import org.sonar.api.resources.Qualifiers; import javax.annotation.Nullable; - import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.List; public class PastSnapshotFinder implements BatchExtension { @@ -196,60 +191,4 @@ public class PastSnapshotFinder implements BatchExtension { } } - public static List getPropertyDefinitions() { - return ImmutableList.of( - PropertyDefinition.builder(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + 1) - .name("Period 1") - .description("Period used to compare measures and track new violations. Values are :
  • Number of days before " + - "analysis, for example 5.
  • A custom date. Format is yyyy-MM-dd, for example 2010-12-25
  • 'previous_analysis' to " + - "compare to previous analysis
  • 'previous_version' to compare to the previous version in the project history
" + - "

When specifying a number of days or a date, the snapshot selected for comparison is " + - " the first one available inside the corresponding time range.

" + - "

Changing this property only takes effect after subsequent project inspections.

") - .defaultValue(CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_1) - .category(CoreProperties.CATEGORY_GENERAL) - .subCategory(CoreProperties.SUBCATEGORY_DIFFERENTIAL_VIEWS) - .build(), - - PropertyDefinition.builder(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + 2) - .name("Period 2") - .description("See the property 'Period 1'") - .defaultValue(CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_2) - .category(CoreProperties.CATEGORY_GENERAL) - .subCategory(CoreProperties.SUBCATEGORY_DIFFERENTIAL_VIEWS) - .build(), - - PropertyDefinition.builder(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + 3) - .name("Period 3") - .description("See the property 'Period 1'") - .defaultValue(CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_3) - .category(CoreProperties.CATEGORY_GENERAL) - .subCategory(CoreProperties.SUBCATEGORY_DIFFERENTIAL_VIEWS) - .build(), - - PropertyDefinition.builder(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + 4) - .name("Period 4") - .description("Period used to compare measures and track new violations. This property is specific to the project. Values are : " + - "

  • Number of days before analysis, for example 5.
  • A custom date. Format is yyyy-MM-dd, " + - "for example 2010-12-25
  • 'previous_analysis' to compare to previous analysis
  • " + - "
  • 'previous_version' to compare to the previous version in the project history
  • A version, for example 1.2
" + - "

When specifying a number of days or a date, the snapshot selected for comparison is the first one available inside the corresponding time range.

" + - "

Changing this property only takes effect after subsequent project inspections.

") - .defaultValue(CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_4) - .onlyOnQualifiers(Qualifiers.PROJECT) - .category(CoreProperties.CATEGORY_GENERAL) - .subCategory(CoreProperties.SUBCATEGORY_DIFFERENTIAL_VIEWS) - .build(), - - PropertyDefinition.builder(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + 5) - .name("Period 5") - .description("See the property 'Period 4'") - .defaultValue(CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_5) - .onlyOnQualifiers(Qualifiers.PROJECT) - .category(CoreProperties.CATEGORY_GENERAL) - .subCategory(CoreProperties.SUBCATEGORY_DIFFERENTIAL_VIEWS) - .build() - ); - } - } diff --git a/sonar-batch/src/main/java/org/sonar/batch/debt/DebtDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/debt/DebtDecorator.java new file mode 100644 index 00000000000..71e9cc36a1c --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/debt/DebtDecorator.java @@ -0,0 +1,215 @@ +/* + * 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.batch.debt; + +import com.google.common.annotations.VisibleForTesting; +import org.sonar.api.batch.Decorator; +import org.sonar.api.batch.DecoratorBarriers; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.batch.DependedUpon; +import org.sonar.api.batch.DependsUpon; +import org.sonar.api.batch.rule.Rule; +import org.sonar.api.batch.rule.Rules; +import org.sonar.api.component.ResourcePerspectives; +import org.sonar.api.issue.Issuable; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.MeasuresFilters; +import org.sonar.api.measures.Metric; +import org.sonar.api.measures.PersistenceMode; +import org.sonar.api.measures.RuleMeasure; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.api.resources.ResourceUtils; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.RuleFinder; +import org.sonar.api.technicaldebt.batch.Characteristic; +import org.sonar.api.technicaldebt.batch.TechnicalDebtModel; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.Maps.newHashMap; + +/** + * Decorator that computes the technical debt metric + */ +@DependsUpon(DecoratorBarriers.ISSUES_TRACKED) +public final class DebtDecorator implements Decorator { + + private final ResourcePerspectives perspectives; + private final TechnicalDebtModel model; + private final Rules rules; + + /** + * ruleFinder is needed to load "old" rule in order to persist rule measure + */ + private final RuleFinder ruleFinder; + + public DebtDecorator(ResourcePerspectives perspectives, TechnicalDebtModel model, Rules rules, RuleFinder ruleFinder) { + this.perspectives = perspectives; + this.model = model; + this.rules = rules; + this.ruleFinder = ruleFinder; + } + + public boolean shouldExecuteOnProject(Project project) { + return true; + } + + @DependedUpon + public List generatesMetrics() { + return Arrays.asList(CoreMetrics.TECHNICAL_DEBT); + } + + public void decorate(Resource resource, DecoratorContext context) { + Issuable issuable = perspectives.as(Issuable.class, resource); + if (issuable != null && shouldSaveMeasure(context)) { + List issues = newArrayList(issuable.issues()); + saveMeasures(context, issues); + } + } + + private void saveMeasures(DecoratorContext context, List issues) { + Long total = 0L; + SumMap ruleDebts = new SumMap(); + SumMap characteristicDebts = new SumMap(); + + // Aggregate rules debt from current issues (and populate current characteristic debt) + for (Issue issue : issues) { + Long debt = ((DefaultIssue) issue).debtInMinutes(); + total += computeDebt(debt, issue.ruleKey(), ruleDebts, characteristicDebts); + } + + // Aggregate rules debt from children (and populate children characteristics debt) + for (Measure measure : context.getChildrenMeasures(MeasuresFilters.rules(CoreMetrics.TECHNICAL_DEBT))) { + Long debt = measure.getValue().longValue(); + RuleMeasure ruleMeasure = (RuleMeasure) measure; + total += computeDebt(debt, ruleMeasure.getRule().ruleKey(), ruleDebts, characteristicDebts); + } + + context.saveMeasure(CoreMetrics.TECHNICAL_DEBT, total.doubleValue()); + saveOnRule(context, ruleDebts); + for (Characteristic characteristic : model.characteristics()) { + Long debt = characteristicDebts.get(characteristic); + saveCharacteristicMeasure(context, characteristic, debt != null ? debt.doubleValue() : 0d, false); + } + } + + private Long computeDebt(@Nullable Long debt, RuleKey ruleKey, SumMap ruleDebts, SumMap characteristicDebts) { + if (debt != null) { + Rule rule = rules.find(ruleKey); + if (rule != null) { + String characteristicKey = rule.debtSubCharacteristic(); + if (characteristicKey != null) { + Characteristic characteristic = model.characteristicByKey(characteristicKey); + if (characteristic != null) { + ruleDebts.add(ruleKey, debt); + characteristicDebts.add(characteristic, debt); + propagateTechnicalDebtInParents(characteristic.parent(), debt, characteristicDebts); + return debt; + } + } + } + } + return 0L; + } + + private void propagateTechnicalDebtInParents(@Nullable Characteristic characteristic, long value, SumMap characteristicDebts) { + if (characteristic != null) { + characteristicDebts.add(characteristic, value); + propagateTechnicalDebtInParents(characteristic.parent(), value, characteristicDebts); + } + } + + private void saveOnRule(DecoratorContext context, SumMap ruleDebts) { + for (Map.Entry entry : ruleDebts.entrySet()) { + org.sonar.api.rules.Rule oldRule = ruleFinder.findByKey(entry.getKey()); + if (oldRule != null) { + saveRuleMeasure(context, oldRule, entry.getValue().doubleValue(), ResourceUtils.isEntity(context.getResource())); + } + } + } + + @VisibleForTesting + void saveCharacteristicMeasure(DecoratorContext context, Characteristic characteristic, Double value, boolean inMemory) { + // we need the value on projects (root or module) even if value==0 in order to display correctly the SQALE history chart (see SQALE-122) + // BUT we don't want to save zero-values for non top-characteristics (see SQALE-147) + if (value > 0.0 || (ResourceUtils.isProject(context.getResource()) && characteristic.isRoot())) { + Measure measure = new Measure(CoreMetrics.TECHNICAL_DEBT); + measure.setCharacteristic(characteristic); + saveMeasure(context, measure, value, inMemory); + } + } + + @VisibleForTesting + void saveRuleMeasure(DecoratorContext context, org.sonar.api.rules.Rule rule, Double value, boolean inMemory) { + // we need the value on projects (root or module) even if value==0 in order to display correctly the SQALE history chart (see SQALE-122) + // BUT we don't want to save zero-values for non top-characteristics (see SQALE-147) + if (value > 0.0) { + RuleMeasure measure = new RuleMeasure(CoreMetrics.TECHNICAL_DEBT, rule, null, null); + saveMeasure(context, measure, value, inMemory); + } + } + + private void saveMeasure(DecoratorContext context, Measure measure, Double value, boolean inMemory) { + measure.setValue(value); + if (inMemory) { + measure.setPersistenceMode(PersistenceMode.MEMORY); + } + context.saveMeasure(measure); + } + + private boolean shouldSaveMeasure(DecoratorContext context) { + return context.getMeasure(CoreMetrics.TECHNICAL_DEBT) == null; + } + + private static class SumMap { + private Map sumByKeys; + + public SumMap() { + sumByKeys = newHashMap(); + } + + public void add(@Nullable E key, Long value) { + if (key != null) { + Long currentValue = sumByKeys.get(key); + sumByKeys.put(key, currentValue != null ? currentValue + value : value); + } + } + + @CheckForNull + public Long get(E key) { + return sumByKeys.get(key); + } + + public Set> entrySet() { + return sumByKeys.entrySet(); + } + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/debt/NewDebtDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/debt/NewDebtDecorator.java new file mode 100644 index 00000000000..fe738d63de8 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/debt/NewDebtDecorator.java @@ -0,0 +1,109 @@ +/* + * 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.batch.debt; + +import com.google.common.collect.ImmutableList; +import org.sonar.api.batch.*; +import org.sonar.api.component.ResourcePerspectives; +import org.sonar.api.issue.Issuable; +import org.sonar.api.issue.Issue; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.MeasureUtils; +import org.sonar.api.measures.Metric; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.batch.components.Period; +import org.sonar.batch.components.TimeMachineConfiguration; +import org.sonar.batch.debt.IssueChangelogDebtCalculator; + +import javax.annotation.Nullable; + +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import static com.google.common.collect.Lists.newArrayList; + +/** + * Decorator that computes the technical debt metric + */ +@DependsUpon(DecoratorBarriers.ISSUES_TRACKED) +public final class NewDebtDecorator implements Decorator { + + private final ResourcePerspectives perspectives; + private final TimeMachineConfiguration timeMachineConfiguration; + private final IssueChangelogDebtCalculator issueChangelogDebtCalculator; + + public NewDebtDecorator(ResourcePerspectives perspectives, TimeMachineConfiguration timeMachineConfiguration, + IssueChangelogDebtCalculator issueChangelogDebtCalculator) { + this.perspectives = perspectives; + this.timeMachineConfiguration = timeMachineConfiguration; + this.issueChangelogDebtCalculator = issueChangelogDebtCalculator; + } + + public boolean shouldExecuteOnProject(Project project) { + return true; + } + + @DependedUpon + public List generatesMetrics() { + return ImmutableList.of( + CoreMetrics.NEW_TECHNICAL_DEBT + ); + } + + public void decorate(Resource resource, DecoratorContext context) { + Issuable issuable = perspectives.as(Issuable.class, resource); + if (issuable != null && shouldSaveNewMetrics(context)) { + List issues = newArrayList(issuable.issues()); + saveMeasures(context, issues); + } + } + + private void saveMeasures(DecoratorContext context, Collection issues) { + Measure measure = new Measure(CoreMetrics.NEW_TECHNICAL_DEBT); + for (Period period : timeMachineConfiguration.periods()) { + Date periodDate = period.getDate(); + double value = calculateNewTechnicalDebtValue(issues, periodDate); + Collection children = context.getChildrenMeasures(measure.getMetric()); + double sum = MeasureUtils.sumOnVariation(true, period.getIndex(), children) + value; + measure.setVariation(period.getIndex(), sum); + } + context.saveMeasure(measure); + } + + private long calculateNewTechnicalDebtValue(Collection issues, @Nullable Date periodDate) { + long result = 0; + for (Issue issue : issues) { + Long debt = issueChangelogDebtCalculator.calculateNewTechnicalDebt(issue, periodDate); + if (debt != null) { + result += debt; + } + } + return result; + } + + private boolean shouldSaveNewMetrics(DecoratorContext context) { + return context.getMeasure(CoreMetrics.NEW_TECHNICAL_DEBT) == null; + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/IssueExclusionsConfiguration.java b/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/IssueExclusionsConfiguration.java deleted file mode 100644 index 18c2f9fa8b4..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/IssueExclusionsConfiguration.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * 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.batch.issue.ignore; - -import com.google.common.collect.ImmutableList; -import org.sonar.api.CoreProperties; -import org.sonar.api.PropertyType; -import org.sonar.api.config.PropertyDefinition; -import org.sonar.api.config.PropertyFieldDefinition; -import org.sonar.api.resources.Qualifiers; - -import java.util.List; - -public final class IssueExclusionsConfiguration { - - public static final String SUB_CATEGORY_IGNORE_ISSUES = "issues"; - - public static final String EXCLUSION_KEY_PREFIX = "sonar.issue.ignore"; - public static final String INCLUSION_KEY_PREFIX = "sonar.issue.enforce"; - - public static final String MULTICRITERIA_SUFFIX = ".multicriteria"; - public static final String PATTERNS_MULTICRITERIA_EXCLUSION_KEY = EXCLUSION_KEY_PREFIX + MULTICRITERIA_SUFFIX; - public static final String PATTERNS_MULTICRITERIA_INCLUSION_KEY = INCLUSION_KEY_PREFIX + MULTICRITERIA_SUFFIX; - public static final String RESOURCE_KEY = "resourceKey"; - private static final String PROPERTY_FILE_PATH_PATTERN = "File Path Pattern"; - public static final String RULE_KEY = "ruleKey"; - private static final String PROPERTY_RULE_KEY_PATTERN = "Rule Key Pattern"; - private static final String PROPERTY_RULE_KEY_PATTERN_HELP = "
A rule key pattern consists of the rule repository name, followed by a colon, followed by a rule key " - + "or rule name fragment. For example:" - + "

  • squid:S1195
  • squid:*Naming*
"; - - public static final String BLOCK_SUFFIX = ".block"; - public static final String PATTERNS_BLOCK_KEY = EXCLUSION_KEY_PREFIX + BLOCK_SUFFIX; - public static final String BEGIN_BLOCK_REGEXP = "beginBlockRegexp"; - public static final String END_BLOCK_REGEXP = "endBlockRegexp"; - - public static final String ALLFILE_SUFFIX = ".allfile"; - public static final String PATTERNS_ALLFILE_KEY = EXCLUSION_KEY_PREFIX + ALLFILE_SUFFIX; - public static final String FILE_REGEXP = "fileRegexp"; - - static final int LARGE_SIZE = 40; - static final int SMALL_SIZE = 10; - - private IssueExclusionsConfiguration() { - // static configuration declaration only - } - - public static List getPropertyDefinitions() { - return ImmutableList.of( - PropertyDefinition.builder(PATTERNS_MULTICRITERIA_EXCLUSION_KEY) - .category(CoreProperties.CATEGORY_EXCLUSIONS) - .subCategory(SUB_CATEGORY_IGNORE_ISSUES) - .name("Ignore Issues on Multiple Criteria") - .description("Patterns to ignore issues on certain components and for certain coding rules." + PROPERTY_RULE_KEY_PATTERN_HELP) - .onQualifiers(Qualifiers.PROJECT) - .index(3) - .fields( - PropertyFieldDefinition.build(RULE_KEY) - .name(PROPERTY_RULE_KEY_PATTERN) - .description("Pattern to match rules which should be ignored.") - .type(PropertyType.STRING) - .indicativeSize(LARGE_SIZE) - .build(), - PropertyFieldDefinition.build(RESOURCE_KEY) - .name(PROPERTY_FILE_PATH_PATTERN) - .description("Pattern to match files which should be ignored.") - .type(PropertyType.STRING) - .indicativeSize(LARGE_SIZE) - .build()) - .build(), - PropertyDefinition.builder(PATTERNS_BLOCK_KEY) - .category(CoreProperties.CATEGORY_EXCLUSIONS) - .subCategory(SUB_CATEGORY_IGNORE_ISSUES) - .name("Ignore Issues in Blocks") - .description("Patterns to ignore all issues on specific blocks of code, while continuing to scan and mark issues on the remainder of the file.") - .onQualifiers(Qualifiers.PROJECT) - .index(2) - .fields( - PropertyFieldDefinition.build(BEGIN_BLOCK_REGEXP) - .name("Regular Expression for Start of Block") - .description("If this regular expression is found in a file, then following lines are ignored until end of block.") - .type(PropertyType.STRING) - .indicativeSize(LARGE_SIZE) - .build(), - PropertyFieldDefinition.build(END_BLOCK_REGEXP) - .name("Regular Expression for End of Block") - .description("If specified, this regular expression is used to determine the end of code blocks to ignore. If not, then block ends at the end of file.") - .type(PropertyType.STRING) - .indicativeSize(LARGE_SIZE) - .build()) - .build(), - PropertyDefinition.builder(PATTERNS_ALLFILE_KEY) - .category(CoreProperties.CATEGORY_EXCLUSIONS) - .subCategory(SUB_CATEGORY_IGNORE_ISSUES) - .name("Ignore Issues on Files") - .description("Patterns to ignore all issues on files that contain a block of code matching a given regular expression.") - .onQualifiers(Qualifiers.PROJECT) - .index(1) - .fields( - PropertyFieldDefinition.build(FILE_REGEXP) - .name("Regular Expression") - .description("If this regular expression is found in a file, then the whole file is ignored.") - .type(PropertyType.STRING) - .indicativeSize(LARGE_SIZE) - .build()) - .build(), - PropertyDefinition.builder(PATTERNS_MULTICRITERIA_INCLUSION_KEY) - .category(CoreProperties.CATEGORY_EXCLUSIONS) - .subCategory(SUB_CATEGORY_IGNORE_ISSUES) - .name("Restrict Scope of Coding Rules") - .description("Patterns to restrict the application of a rule to only certain components, ignoring all others." + PROPERTY_RULE_KEY_PATTERN_HELP) - .onQualifiers(Qualifiers.PROJECT) - .index(4) - .fields( - PropertyFieldDefinition.build(RULE_KEY) - .name(PROPERTY_RULE_KEY_PATTERN) - .description("Pattern used to match rules which should be restricted.") - .type(PropertyType.STRING) - .indicativeSize(LARGE_SIZE) - .build(), - PropertyFieldDefinition.build(RESOURCE_KEY) - .name(PROPERTY_FILE_PATH_PATTERN) - .description("Pattern used to match files to which the rules should be restricted.") - .type(PropertyType.STRING) - .indicativeSize(LARGE_SIZE) - .build()) - .build()); - } -} diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/AbstractPatternInitializer.java b/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/AbstractPatternInitializer.java index 03d218b3a3b..e29fc5aaa30 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/AbstractPatternInitializer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/AbstractPatternInitializer.java @@ -19,8 +19,6 @@ */ package org.sonar.batch.issue.ignore.pattern; -import org.sonar.batch.issue.ignore.IssueExclusionsConfiguration; - import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import org.apache.commons.lang.StringUtils; @@ -67,8 +65,8 @@ public abstract class AbstractPatternInitializer implements BatchExtension { String patternConf = StringUtils.defaultIfBlank(settings.getString(getMulticriteriaConfigurationKey()), ""); for (String id : StringUtils.split(patternConf, ',')) { String propPrefix = getMulticriteriaConfigurationKey() + "." + id + "."; - String resourceKeyPattern = settings.getString(propPrefix + IssueExclusionsConfiguration.RESOURCE_KEY); - String ruleKeyPattern = settings.getString(propPrefix + IssueExclusionsConfiguration.RULE_KEY); + String resourceKeyPattern = settings.getString(propPrefix + "resourceKey"); + String ruleKeyPattern = settings.getString(propPrefix + "ruleKey"); String lineRange = "*"; String[] fields = new String[] { resourceKeyPattern, ruleKeyPattern, lineRange }; PatternDecoder.checkRegularLineConstraints(StringUtils.join(fields, ","), fields); diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializer.java b/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializer.java index 79b176b4721..75882be5422 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializer.java @@ -20,12 +20,11 @@ package org.sonar.batch.issue.ignore.pattern; -import org.sonar.batch.issue.ignore.IssueExclusionsConfiguration; - import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import org.apache.commons.lang.StringUtils; import org.sonar.api.config.Settings; +import org.sonar.core.config.IssueExclusionProperties; import java.util.List; @@ -45,7 +44,7 @@ public class IssueExclusionPatternInitializer extends AbstractPatternInitializer @Override protected String getMulticriteriaConfigurationKey() { - return IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY; + return "sonar.issue.ignore" + ".multicriteria"; } public PatternMatcher getPatternMatcher() { @@ -54,7 +53,7 @@ public class IssueExclusionPatternInitializer extends AbstractPatternInitializer @Override public void initializePatternsForPath(String relativePath, String componentKey) { - for (IssuePattern pattern: getMulticriteriaPatterns()) { + for (IssuePattern pattern : getMulticriteriaPatterns()) { if (pattern.matchResource(relativePath)) { getPatternMatcher().addPatternForComponent(componentKey, pattern); } @@ -70,12 +69,12 @@ public class IssueExclusionPatternInitializer extends AbstractPatternInitializer protected final void loadFileContentPatterns() { // Patterns Block blockPatterns = Lists.newArrayList(); - String patternConf = StringUtils.defaultIfBlank(getSettings().getString(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY), ""); + String patternConf = StringUtils.defaultIfBlank(getSettings().getString(IssueExclusionProperties.PATTERNS_BLOCK_KEY), ""); for (String id : StringUtils.split(patternConf, ',')) { - String propPrefix = IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY + "." + id + "."; - String beginBlockRegexp = getSettings().getString(propPrefix + IssueExclusionsConfiguration.BEGIN_BLOCK_REGEXP); - String endBlockRegexp = getSettings().getString(propPrefix + IssueExclusionsConfiguration.END_BLOCK_REGEXP); - String[] fields = new String[] { beginBlockRegexp, endBlockRegexp }; + String propPrefix = IssueExclusionProperties.PATTERNS_BLOCK_KEY + "." + id + "."; + String beginBlockRegexp = getSettings().getString(propPrefix + IssueExclusionProperties.BEGIN_BLOCK_REGEXP); + String endBlockRegexp = getSettings().getString(propPrefix + IssueExclusionProperties.END_BLOCK_REGEXP); + String[] fields = new String[]{beginBlockRegexp, endBlockRegexp}; PatternDecoder.checkDoubleRegexpLineConstraints(StringUtils.join(fields, ","), fields); IssuePattern pattern = new IssuePattern().setBeginBlockRegexp(nullToEmpty(beginBlockRegexp)).setEndBlockRegexp(nullToEmpty(endBlockRegexp)); blockPatterns.add(pattern); @@ -83,10 +82,10 @@ public class IssueExclusionPatternInitializer extends AbstractPatternInitializer // Patterns All File allFilePatterns = Lists.newArrayList(); - patternConf = StringUtils.defaultIfBlank(getSettings().getString(IssueExclusionsConfiguration.PATTERNS_ALLFILE_KEY), ""); + patternConf = StringUtils.defaultIfBlank(getSettings().getString(IssueExclusionProperties.PATTERNS_ALLFILE_KEY), ""); for (String id : StringUtils.split(patternConf, ',')) { - String propPrefix = IssueExclusionsConfiguration.PATTERNS_ALLFILE_KEY + "." + id + "."; - String allFileRegexp = getSettings().getString(propPrefix + IssueExclusionsConfiguration.FILE_REGEXP); + String propPrefix = IssueExclusionProperties.PATTERNS_ALLFILE_KEY + "." + id + "."; + String allFileRegexp = getSettings().getString(propPrefix + IssueExclusionProperties.FILE_REGEXP); PatternDecoder.checkWholeFileRegexp(allFileRegexp); IssuePattern pattern = new IssuePattern().setAllFileRegexp(nullToEmpty(allFileRegexp)); allFilePatterns.add(pattern); @@ -102,6 +101,6 @@ public class IssueExclusionPatternInitializer extends AbstractPatternInitializer } public boolean hasFileContentPattern() { - return ! (blockPatterns.isEmpty() && allFilePatterns.isEmpty()); + return !(blockPatterns.isEmpty() && allFilePatterns.isEmpty()); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializer.java b/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializer.java index 5ce390af0be..bf87ad85313 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializer.java @@ -20,8 +20,6 @@ package org.sonar.batch.issue.ignore.pattern; -import org.sonar.batch.issue.ignore.IssueExclusionsConfiguration; - import com.google.common.collect.Maps; import org.sonar.api.config.Settings; @@ -38,7 +36,7 @@ public class IssueInclusionPatternInitializer extends AbstractPatternInitializer @Override protected String getMulticriteriaConfigurationKey() { - return IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_INCLUSION_KEY; + return "sonar.issue.enforce" + ".multicriteria"; } @Override diff --git a/sonar-batch/src/main/java/org/sonar/batch/maven/DefaultMavenPluginExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/maven/DefaultMavenPluginExecutor.java new file mode 100644 index 00000000000..fb0a1a56a20 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/maven/DefaultMavenPluginExecutor.java @@ -0,0 +1,149 @@ +/* + * 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.batch.maven; + +import org.apache.maven.execution.MavenSession; +import org.apache.maven.execution.ReactorManager; +import org.apache.maven.lifecycle.LifecycleExecutor; +import org.apache.maven.project.MavenProject; +import org.sonar.api.batch.SupportedEnvironment; +import org.sonar.api.batch.maven.MavenPlugin; +import org.sonar.api.batch.maven.MavenPluginHandler; +import org.sonar.api.resources.Project; +import org.sonar.api.utils.SonarException; +import org.sonar.api.utils.TimeProfiler; +import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem; +import org.sonar.batch.scan.maven.MavenPluginExecutor; + +import javax.annotation.Nullable; +import java.lang.reflect.Method; +import java.util.Arrays; + +@SupportedEnvironment("maven") +public class DefaultMavenPluginExecutor implements MavenPluginExecutor { + + private final MavenProjectConverter pomConverter; + private LifecycleExecutor lifecycleExecutor; + private MavenSession mavenSession; + + public DefaultMavenPluginExecutor(LifecycleExecutor le, MavenSession mavenSession, MavenProjectConverter pomConverter) { + this.lifecycleExecutor = le; + this.mavenSession = mavenSession; + this.pomConverter = pomConverter; + } + + @Override + public final MavenPluginHandler execute(Project project, DefaultModuleFileSystem fs, MavenPluginHandler handler) { + for (String goal : handler.getGoals()) { + if (goal == null) { + throw new IllegalStateException("Maven goal can't be null"); + } + MavenPlugin plugin = MavenPlugin.getPlugin(project.getPom(), handler.getGroupId(), handler.getArtifactId()); + execute(project, + fs, + getGoal(handler.getGroupId(), handler.getArtifactId(), plugin != null && plugin.getPlugin() != null ? plugin.getPlugin().getVersion() : null, goal)); + } + return handler; + } + + @Override + public final void execute(Project project, DefaultModuleFileSystem fs, String goal) { + if (project.getPom() != null) { + TimeProfiler profiler = new TimeProfiler().start("Execute " + goal); + ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); + try { + concreteExecute(project.getPom(), goal); + } catch (Exception e) { + throw new SonarException("Unable to execute maven plugin", e); + } finally { + // Reset original ClassLoader that may have been changed during Maven Execution (see SONAR-1800) + Thread.currentThread().setContextClassLoader(currentClassLoader); + profiler.stop(); + } + if (!fs.isInitialized()) { + pomConverter.synchronizeFileSystem(project.getPom(), fs); + } + } + } + + static String getGoal(String groupId, String artifactId, @Nullable String version, String goal) { + String defaultVersion = version == null ? "" : version; + return new StringBuilder() + .append(groupId).append(":") + .append(artifactId).append(":") + .append(defaultVersion) + .append(":") + .append(goal) + .toString(); + } + + public void concreteExecute(MavenProject pom, String goal) { + Method executeMethod = null; + for (Method m : lifecycleExecutor.getClass().getMethods()) { + if ("execute".equals(m.getName())) { + executeMethod = m; + break; + } + } + if (executeMethod == null) { + throw new SonarException("Unable to find execute method on Maven LifecycleExecutor. Please check your Maven version."); + } + if (executeMethod.getParameterTypes().length == 1) { + concreteExecuteMaven3(pom, goal); + } else if (executeMethod.getParameterTypes().length == 3) { + concreteExecuteMaven2(executeMethod, pom, goal); + } else { + throw new SonarException("Unexpected parameter count on Maven LifecycleExecutor#execute method. Please check your Maven version."); + } + } + + public void concreteExecuteMaven3(MavenProject pom, String goal) { + MavenSession projectSession = mavenSession.clone(); + projectSession.setCurrentProject(pom); + projectSession.setProjects(Arrays.asList(pom)); + projectSession.getRequest().setRecursive(false); + projectSession.getRequest().setPom(pom.getFile()); + projectSession.getRequest().setGoals(Arrays.asList(goal)); + projectSession.getRequest().setInteractiveMode(false); + lifecycleExecutor.execute(projectSession); + if (projectSession.getResult().hasExceptions()) { + throw new SonarException("Exception during execution of " + goal); + } + } + + public void concreteExecuteMaven2(Method executeMethod, MavenProject pom, String goal) { + try { + ReactorManager reactor = new ReactorManager(Arrays.asList(pom)); + MavenSession clonedSession = new MavenSession(mavenSession.getContainer(), + mavenSession.getSettings(), + mavenSession.getLocalRepository(), + mavenSession.getEventDispatcher(), + reactor, + Arrays.asList(goal), + mavenSession.getExecutionRootDirectory(), + mavenSession.getExecutionProperties(), + mavenSession.getStartTime()); + executeMethod.invoke(lifecycleExecutor, clonedSession, reactor, clonedSession.getEventDispatcher()); + } catch (Exception e) { + throw new SonarException("Unable to execute Maven 2 plugin", e); + } + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectBootstrapper.java b/sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectBootstrapper.java new file mode 100644 index 00000000000..3cd1f9d5c7f --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectBootstrapper.java @@ -0,0 +1,62 @@ +/* + * 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.batch.maven; + +import org.apache.maven.execution.MavenSession; +import org.apache.maven.project.MavenProject; +import org.sonar.api.batch.SupportedEnvironment; +import org.sonar.api.batch.bootstrap.ProjectBootstrapper; +import org.sonar.api.batch.bootstrap.ProjectReactor; + +import java.util.List; + +/** + * @deprecated since 4.3 kept only to support old version of SonarQube Mojo + */ +@Deprecated +@SupportedEnvironment("maven") +public class MavenProjectBootstrapper implements ProjectBootstrapper { + + private MavenSession session; + private MavenProjectConverter mavenProjectConverter; + + public MavenProjectBootstrapper(MavenSession session, MavenProjectConverter mavenProjectConverter) { + this.session = session; + this.mavenProjectConverter = mavenProjectConverter; + } + + @Override + public ProjectReactor bootstrap() { + // Don't use session.getTopLevelProject or session.getProjects to keep compatibility with Maven 2 + List sortedProjects = session.getSortedProjects(); + MavenProject topLevelProject = null; + for (MavenProject project : sortedProjects) { + if (project.isExecutionRoot()) { + topLevelProject = project; + break; + } + } + if (topLevelProject == null) { + throw new IllegalStateException("Maven session does not declare a top level project"); + } + return new ProjectReactor(mavenProjectConverter.configure(sortedProjects, topLevelProject)); + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectBuilder.java b/sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectBuilder.java new file mode 100644 index 00000000000..7cbf3efc5f2 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectBuilder.java @@ -0,0 +1,63 @@ +/* + * 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.batch.maven; + +import org.apache.maven.execution.MavenSession; +import org.apache.maven.project.MavenProject; +import org.sonar.api.batch.SupportedEnvironment; +import org.sonar.api.batch.bootstrap.ProjectBuilder; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.bootstrap.ProjectReactor; + +import java.util.List; + +/** + * Class that inject MavenProject in each module container + */ +@SupportedEnvironment("maven") +public class MavenProjectBuilder extends ProjectBuilder { + + private final MavenSession mavenSession; + + public MavenProjectBuilder(MavenSession mavenSession) { + this.mavenSession = mavenSession; + } + + @Override + public void build(Context context) { + ProjectReactor reactor = context.projectReactor(); + for (ProjectDefinition moduleDef : reactor.getProjects()) { + setMavenProjectIfApplicable(moduleDef); + } + } + + private void setMavenProjectIfApplicable(ProjectDefinition definition) { + if (mavenSession != null) { + String moduleKey = definition.getKey(); + for (MavenProject mavenModule : (List) mavenSession.getSortedProjects()) { + String mavenModuleKey = mavenModule.getGroupId() + ":" + mavenModule.getArtifactId(); + if (mavenModuleKey.equals(moduleKey) && !definition.getContainerExtensions().contains(mavenModule)) { + definition.addContainerExtension(mavenModule); + } + } + } + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectConverter.java b/sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectConverter.java new file mode 100644 index 00000000000..2ef3fe6fa54 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/maven/MavenProjectConverter.java @@ -0,0 +1,310 @@ +/* + * 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.batch.maven; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.apache.commons.lang.StringUtils; +import org.apache.maven.model.CiManagement; +import org.apache.maven.model.IssueManagement; +import org.apache.maven.model.Scm; +import org.apache.maven.project.MavenProject; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.SupportedEnvironment; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.maven.MavenUtils; +import org.sonar.api.task.TaskExtension; +import org.sonar.api.utils.MessageException; +import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem; +import org.sonar.java.api.JavaUtils; + +import javax.annotation.Nullable; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * @deprecated since 4.3 kept only to support old version of SonarQube Mojo + */ +@Deprecated +@SupportedEnvironment("maven") +public class MavenProjectConverter implements TaskExtension { + + private static final String UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE = "Unable to determine structure of project." + + " Probably you use Maven Advanced Reactor Options, which is not supported by SonarQube and should not be used."; + + public ProjectDefinition configure(List poms, MavenProject root) { + // projects by canonical path to pom.xml + Map paths = Maps.newHashMap(); + Map defs = Maps.newHashMap(); + + try { + configureModules(poms, paths, defs); + + rebuildModuleHierarchy(paths, defs); + } catch (IOException e) { + throw new IllegalStateException("Cannot configure project", e); + } + + ProjectDefinition rootProject = defs.get(root); + if (rootProject == null) { + throw new IllegalStateException(UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE); + } + return rootProject; + } + + private void rebuildModuleHierarchy(Map paths, Map defs) throws IOException { + for (Map.Entry entry : paths.entrySet()) { + MavenProject pom = entry.getValue(); + for (Object m : pom.getModules()) { + String moduleId = (String) m; + File modulePath = new File(pom.getBasedir(), moduleId); + MavenProject module = findMavenProject(modulePath, paths); + + ProjectDefinition parentProject = defs.get(pom); + if (parentProject == null) { + throw new IllegalStateException(UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE); + } + ProjectDefinition subProject = defs.get(module); + if (subProject == null) { + throw new IllegalStateException(UNABLE_TO_DETERMINE_PROJECT_STRUCTURE_EXCEPTION_MESSAGE); + } + parentProject.addSubProject(subProject); + } + } + } + + private void configureModules(List poms, Map paths, Map defs) throws IOException { + for (MavenProject pom : poms) { + paths.put(pom.getFile().getCanonicalPath(), pom); + ProjectDefinition def = ProjectDefinition.create(); + merge(pom, def); + defs.put(pom, def); + } + } + + private static MavenProject findMavenProject(final File modulePath, Map paths) throws IOException { + if (modulePath.exists() && modulePath.isDirectory()) { + for (Map.Entry entry : paths.entrySet()) { + String pomFileParentDir = new File(entry.getKey()).getParent(); + if (pomFileParentDir.equals(modulePath.getCanonicalPath())) { + return entry.getValue(); + } + } + return null; + } + return paths.get(modulePath.getCanonicalPath()); + } + + @VisibleForTesting + void merge(MavenProject pom, ProjectDefinition definition) { + String key = getSonarKey(pom); + // IMPORTANT NOTE : reference on properties from POM model must not be saved, + // instead they should be copied explicitly - see SONAR-2896 + definition + .setProperties(pom.getModel().getProperties()) + .setKey(key) + .setVersion(pom.getVersion()) + .setName(pom.getName()) + .setDescription(pom.getDescription()) + .addContainerExtension(pom); + guessJavaVersion(pom, definition); + guessEncoding(pom, definition); + convertMavenLinksToProperties(definition, pom); + synchronizeFileSystem(pom, definition); + } + + private static String getSonarKey(MavenProject pom) { + return new StringBuilder().append(pom.getGroupId()).append(":").append(pom.getArtifactId()).toString(); + } + + private static void guessEncoding(MavenProject pom, ProjectDefinition definition) { + // See http://jira.codehaus.org/browse/SONAR-2151 + String encoding = MavenUtils.getSourceEncoding(pom); + if (encoding != null) { + definition.setProperty(CoreProperties.ENCODING_PROPERTY, encoding); + } + } + + private static void guessJavaVersion(MavenProject pom, ProjectDefinition definition) { + // See http://jira.codehaus.org/browse/SONAR-2148 + // Get Java source and target versions from maven-compiler-plugin. + String version = MavenUtils.getJavaSourceVersion(pom); + if (version != null) { + definition.setProperty(JavaUtils.JAVA_SOURCE_PROPERTY, version); + } + version = MavenUtils.getJavaVersion(pom); + if (version != null) { + definition.setProperty(JavaUtils.JAVA_TARGET_PROPERTY, version); + } + } + + /** + * For SONAR-3676 + */ + private static void convertMavenLinksToProperties(ProjectDefinition definition, MavenProject pom) { + setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_HOME_PAGE, pom.getUrl()); + + Scm scm = pom.getScm(); + if (scm == null) { + scm = new Scm(); + } + setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_SOURCES, scm.getUrl()); + setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_SOURCES_DEV, scm.getDeveloperConnection()); + + CiManagement ci = pom.getCiManagement(); + if (ci == null) { + ci = new CiManagement(); + } + setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_CI, ci.getUrl()); + + IssueManagement issues = pom.getIssueManagement(); + if (issues == null) { + issues = new IssueManagement(); + } + setPropertyIfNotAlreadyExists(definition, CoreProperties.LINKS_ISSUE_TRACKER, issues.getUrl()); + } + + private static void setPropertyIfNotAlreadyExists(ProjectDefinition definition, String propertyKey, String propertyValue) { + if (StringUtils.isBlank(definition.getProperties().getProperty(propertyKey))) { + definition.setProperty(propertyKey, StringUtils.defaultString(propertyValue)); + } + } + + public void synchronizeFileSystem(MavenProject pom, ProjectDefinition into) { + into.setBaseDir(pom.getBasedir()); + File buildDir = getBuildDir(pom); + if (buildDir != null) { + into.setBuildDir(buildDir); + into.setWorkDir(getSonarWorkDir(pom)); + } + into.setSourceDirs(toPaths(mainDirs(pom))); + into.setTestDirs(toPaths(testDirs(pom))); + File binaryDir = resolvePath(pom.getBuild().getOutputDirectory(), pom.getBasedir()); + if (binaryDir != null) { + into.addBinaryDir(binaryDir); + } + } + + public static File getSonarWorkDir(MavenProject pom) { + return new File(getBuildDir(pom), "sonar"); + } + + private static File getBuildDir(MavenProject pom) { + return resolvePath(pom.getBuild().getDirectory(), pom.getBasedir()); + } + + public void synchronizeFileSystem(MavenProject pom, DefaultModuleFileSystem into) { + into.resetDirs( + pom.getBasedir(), + getBuildDir(pom), + mainDirs(pom), + testDirs(pom), + Arrays.asList(resolvePath(pom.getBuild().getOutputDirectory(), pom.getBasedir()))); + } + + static File resolvePath(@Nullable String path, File basedir) { + if (path != null) { + File file = new File(StringUtils.trim(path)); + if (!file.isAbsolute()) { + try { + file = new File(basedir, path).getCanonicalFile(); + } catch (IOException e) { + throw new IllegalStateException("Unable to resolve path '" + path + "'", e); + } + } + return file; + } + return null; + } + + static List resolvePaths(List paths, File basedir) { + List result = Lists.newArrayList(); + for (String path : paths) { + File dir = resolvePath(path, basedir); + if (dir != null) { + result.add(dir); + } + } + return result; + } + + private List mainDirs(MavenProject pom) { + return sourceDirs(pom, ProjectDefinition.SOURCE_DIRS_PROPERTY, pom.getCompileSourceRoots()); + } + + private List testDirs(MavenProject pom) { + return sourceDirs(pom, ProjectDefinition.TEST_DIRS_PROPERTY, pom.getTestCompileSourceRoots()); + } + + private List sourceDirs(MavenProject pom, String propertyKey, List mavenDirs) { + List paths; + String prop = pom.getProperties().getProperty(propertyKey); + if (prop != null) { + paths = Arrays.asList(StringUtils.split(prop, ",")); + // do not remove dirs that do not exist. They must be kept in order to + // notify users that value of sonar.sources has a typo. + return existingDirsOrFail(resolvePaths(paths, pom.getBasedir()), pom, propertyKey); + } + + List dirs = resolvePaths(mavenDirs, pom.getBasedir()); + + // Maven provides some directories that do not exist. They + // should be removed + return keepExistingDirs(dirs); + } + + private List existingDirsOrFail(List dirs, MavenProject pom, String propertyKey) { + for (File dir : dirs) { + if (!dir.isDirectory() || !dir.exists()) { + throw MessageException.of(String.format( + "The directory '%s' does not exist for Maven module %s. Please check the property %s", + dir.getAbsolutePath(), pom.getId(), propertyKey)); + } + } + return dirs; + } + + private static List keepExistingDirs(List files) { + return Lists.newArrayList(Collections2.filter(files, new Predicate() { + @Override + public boolean apply(File dir) { + return dir != null && dir.exists() && dir.isDirectory(); + } + })); + } + + private static String[] toPaths(Collection dirs) { + Collection paths = Collections2.transform(dirs, new Function() { + @Override + public String apply(File dir) { + return dir.getAbsolutePath(); + } + }); + return paths.toArray(new String[paths.size()]); + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/maven/package-info.java b/sonar-batch/src/main/java/org/sonar/batch/maven/package-info.java new file mode 100644 index 00000000000..b595cf0548f --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/maven/package-info.java @@ -0,0 +1,28 @@ +/* + * 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. + */ + +/** + * This package is a part of bootstrap process, so we should take care about backward compatibility. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.maven; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java index 835aa9be91f..5180a498752 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java @@ -19,6 +19,7 @@ */ package org.sonar.batch.scan; +import org.sonar.api.BatchComponent; import org.sonar.batch.qualitygate.GenerateQualityGateEvents; import org.slf4j.Logger; @@ -158,7 +159,7 @@ public class ModuleScanContainer extends ComponentContainer { ExtensionInstaller installer = getComponentByType(ExtensionInstaller.class); installer.install(this, new ExtensionMatcher() { public boolean accept(Object extension) { - if (ExtensionUtils.isType(extension, BatchExtension.class) && ExtensionUtils.isInstantiationStrategy(extension, InstantiationStrategy.PER_PROJECT)) { + if (ExtensionUtils.isType(extension, BatchComponent.class) && ExtensionUtils.isInstantiationStrategy(extension, InstantiationStrategy.PER_PROJECT)) { // Special use-case: the extension point ProjectBuilder is used in a Maven environment to define some // new sub-projects without pom. // Example : C# plugin adds sub-projects at runtime, even if they are not defined in root pom. diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java index dee73ecd6c7..e4eacf6aa8d 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java @@ -20,6 +20,7 @@ package org.sonar.batch.scan; import com.google.common.annotations.VisibleForTesting; +import org.sonar.api.BatchComponent; import org.sonar.api.BatchExtension; import org.sonar.api.CoreProperties; import org.sonar.api.batch.InstantiationStrategy; @@ -201,7 +202,7 @@ public class ProjectScanContainer extends ComponentContainer { static class BatchExtensionFilter implements ExtensionMatcher { public boolean accept(Object extension) { - return ExtensionUtils.isType(extension, BatchExtension.class) + return ExtensionUtils.isType(extension, BatchComponent.class) && ExtensionUtils.isInstantiationStrategy(extension, InstantiationStrategy.PER_BATCH); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/debt/DebtDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/debt/DebtDecoratorTest.java new file mode 100644 index 00000000000..fc82ece746c --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/debt/DebtDecoratorTest.java @@ -0,0 +1,355 @@ +/* + * 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.batch.debt; + +import com.google.common.collect.Lists; +import org.apache.commons.lang.ObjectUtils; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; +import org.hamcrest.Description; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.batch.rule.Rules; +import org.sonar.api.batch.rule.internal.RulesBuilder; +import org.sonar.api.component.ResourcePerspectives; +import org.sonar.api.issue.Issuable; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.MeasuresFilter; +import org.sonar.api.measures.Metric; +import org.sonar.api.measures.RuleMeasure; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.RuleFinder; +import org.sonar.api.technicaldebt.batch.Characteristic; +import org.sonar.api.technicaldebt.batch.TechnicalDebtModel; +import org.sonar.api.technicaldebt.batch.internal.DefaultCharacteristic; +import org.sonar.api.test.IsMeasure; +import org.sonar.api.utils.Duration; + +import java.util.Collections; + +import static com.google.common.collect.Lists.newArrayList; +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class DebtDecoratorTest { + + static final int HOURS_IN_DAY = 8; + + static final Long ONE_DAY_IN_MINUTES = 1L * HOURS_IN_DAY * 60; + + @Mock + DecoratorContext context; + + @Mock + Resource resource; + + @Mock + TechnicalDebtModel debtModel; + + @Mock + Issuable issuable; + + @Mock + ResourcePerspectives perspectives; + + @Mock + RuleFinder ruleFinder; + + RuleKey ruleKey1 = RuleKey.of("repo1", "rule1"); + RuleKey ruleKey2 = RuleKey.of("repo2", "rule2"); + Rules rules; + + DefaultCharacteristic efficiency = new DefaultCharacteristic().setKey("EFFICIENCY"); + DefaultCharacteristic memoryEfficiency = new DefaultCharacteristic().setKey("MEMORY_EFFICIENCY").setParent(efficiency); + + DefaultCharacteristic reusability = new DefaultCharacteristic().setKey("REUSABILITY"); + DefaultCharacteristic modularity = new DefaultCharacteristic().setKey("MODULARITY").setParent(reusability); + + DebtDecorator decorator; + + @Before + public void before() throws Exception { + when(perspectives.as(Issuable.class, resource)).thenReturn(issuable); + RulesBuilder rulesBuilder = new RulesBuilder(); + rulesBuilder.add(ruleKey1).setName("rule1").setDebtSubCharacteristic("MEMORY_EFFICIENCY"); + rulesBuilder.add(ruleKey2).setName("rule2").setDebtSubCharacteristic("MODULARITY"); + rules = rulesBuilder.build(); + + when(ruleFinder.findByKey(ruleKey1)).thenReturn(org.sonar.api.rules.Rule.create(ruleKey1.repository(), ruleKey1.rule())); + when(ruleFinder.findByKey(ruleKey2)).thenReturn(org.sonar.api.rules.Rule.create(ruleKey2.repository(), ruleKey2.rule())); + + when(debtModel.characteristics()).thenReturn(newArrayList(efficiency, memoryEfficiency, reusability, modularity)); + when(debtModel.characteristicByKey("EFFICIENCY")).thenReturn(efficiency); + when(debtModel.characteristicByKey("MEMORY_EFFICIENCY")).thenReturn(memoryEfficiency); + when(debtModel.characteristicByKey("REUSABILITY")).thenReturn(reusability); + when(debtModel.characteristicByKey("MODULARITY")).thenReturn(modularity); + + decorator = new DebtDecorator(perspectives, debtModel, rules, ruleFinder); + } + + @Test + public void generates_metrics() throws Exception { + assertThat(decorator.generatesMetrics()).hasSize(1); + } + + @Test + public void execute_on_project() throws Exception { + assertThat(decorator.shouldExecuteOnProject(null)).isTrue(); + } + + @Test + public void not_save_if_measure_already_computed() { + when(context.getMeasure(CoreMetrics.TECHNICAL_DEBT)).thenReturn(new Measure()); + + decorator.decorate(resource, context); + + verify(context, never()).saveMeasure(argThat(new IsMeasure(CoreMetrics.TECHNICAL_DEBT))); + } + + @Test + public void add_technical_debt_from_one_issue_and_no_parent() throws Exception { + Issue issue = createIssue("rule1", "repo1").setDebt(Duration.create(ONE_DAY_IN_MINUTES)); + when(issuable.issues()).thenReturn(newArrayList(issue)); + + decorator.decorate(resource, context); + + verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, ONE_DAY_IN_MINUTES.doubleValue()); + verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey1, ONE_DAY_IN_MINUTES.doubleValue()))); + } + + @Test + public void add_technical_debt_from_one_issue_without_debt() throws Exception { + Issue issue = createIssue("rule1", "repo1").setDebt(null); + when(issuable.issues()).thenReturn(newArrayList(issue)); + + decorator.decorate(resource, context); + + verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, 0.0); + } + + @Test + public void add_technical_debt_from_one_issue_and_propagate_to_parents() throws Exception { + Issue issue = createIssue("rule1", "repo1").setDebt(Duration.create(ONE_DAY_IN_MINUTES)); + when(issuable.issues()).thenReturn(newArrayList(issue)); + + decorator.decorate(resource, context); + + verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, ONE_DAY_IN_MINUTES.doubleValue()); + verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey1, ONE_DAY_IN_MINUTES.doubleValue()))); + verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, efficiency, ONE_DAY_IN_MINUTES.doubleValue()))); + verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, memoryEfficiency, ONE_DAY_IN_MINUTES.doubleValue()))); + } + + @Test + public void add_technical_debt_from_issues() throws Exception { + Long technicalDebt1 = ONE_DAY_IN_MINUTES; + Long technicalDebt2 = 2 * ONE_DAY_IN_MINUTES; + + Issue issue1 = createIssue("rule1", "repo1").setDebt(Duration.create(technicalDebt1)); + Issue issue2 = createIssue("rule1", "repo1").setDebt(Duration.create(technicalDebt1)); + Issue issue3 = createIssue("rule2", "repo2").setDebt(Duration.create(technicalDebt2)); + Issue issue4 = createIssue("rule2", "repo2").setDebt(Duration.create(technicalDebt2)); + when(issuable.issues()).thenReturn(newArrayList(issue1, issue2, issue3, issue4)); + + decorator.decorate(resource, context); + + verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, 6d * ONE_DAY_IN_MINUTES); + verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey1, 2d * ONE_DAY_IN_MINUTES))); + verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey2, 4d * ONE_DAY_IN_MINUTES))); + } + + @Test + public void add_technical_debt_from_current_and_children_measures() throws Exception { + Issue issue1 = createIssue("rule1", "repo1").setDebt(Duration.create(ONE_DAY_IN_MINUTES)); + Issue issue2 = createIssue("rule1", "repo1").setDebt(Duration.create(ONE_DAY_IN_MINUTES)); + when(issuable.issues()).thenReturn(newArrayList(issue1, issue2)); + + when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Lists.newArrayList( + new RuleMeasure(CoreMetrics.TECHNICAL_DEBT, + org.sonar.api.rules.Rule.create(ruleKey1.repository(), ruleKey1.rule()), null, null) + .setValue(5d * ONE_DAY_IN_MINUTES) + )); + decorator.decorate(resource, context); + + verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, 7d * ONE_DAY_IN_MINUTES); + verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey1, 7d * ONE_DAY_IN_MINUTES))); + verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, memoryEfficiency, 7d * ONE_DAY_IN_MINUTES))); + verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, efficiency, 7d * ONE_DAY_IN_MINUTES))); + } + + @Test + public void add_technical_debt_only_from_children_measures() throws Exception { + when(issuable.issues()).thenReturn(Collections.emptyList()); + + when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Lists.newArrayList( + new RuleMeasure(CoreMetrics.TECHNICAL_DEBT, + org.sonar.api.rules.Rule.create(ruleKey1.repository(), ruleKey1.rule()) + , null, null).setValue(5d * ONE_DAY_IN_MINUTES), + + new RuleMeasure(CoreMetrics.TECHNICAL_DEBT, + org.sonar.api.rules.Rule.create(ruleKey2.repository(), ruleKey2.rule()) + , null, null).setValue(10d * ONE_DAY_IN_MINUTES) + )); + decorator.decorate(resource, context); + + verify(context).saveMeasure(CoreMetrics.TECHNICAL_DEBT, 15d * ONE_DAY_IN_MINUTES); + verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey1, 5d * ONE_DAY_IN_MINUTES))); + verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.TECHNICAL_DEBT, ruleKey2, 10d * ONE_DAY_IN_MINUTES))); + verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, memoryEfficiency, 5d * ONE_DAY_IN_MINUTES))); + verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, efficiency, 5d * ONE_DAY_IN_MINUTES))); + verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, reusability, 10d * ONE_DAY_IN_MINUTES))); + verify(context).saveMeasure(argThat(new IsCharacteristicMeasure(CoreMetrics.TECHNICAL_DEBT, modularity, 10d * ONE_DAY_IN_MINUTES))); + } + + @Test + public void always_save_technical_debt_for_positive_values() throws Exception { + // for a project + DecoratorContext context = mock(DecoratorContext.class); + when(context.getResource()).thenReturn(new Project("foo")); + decorator.saveCharacteristicMeasure(context, (Characteristic) null, 12.0, false); + verify(context, times(1)).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT)); + + // or for a file + context = mock(DecoratorContext.class); + when(context.getResource()).thenReturn(new File("foo")); + decorator.saveCharacteristicMeasure(context, (Characteristic) null, 12.0, false); + verify(context, times(1)).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT)); + } + + @Test + public void always_save_technical_debt_for_project_if_top_characteristic() throws Exception { + DecoratorContext context = mock(DecoratorContext.class); + when(context.getResource()).thenReturn(new Project("foo")); + + // this is a top characteristic + DefaultCharacteristic rootCharacteristic = new DefaultCharacteristic().setKey("root"); + + decorator.saveCharacteristicMeasure(context, rootCharacteristic, 0.0, true); + verify(context, times(1)).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT).setCharacteristic(rootCharacteristic)); + } + + /** + * SQALE-147 + */ + @Test + public void never_save_technical_debt_for_project_if_not_top_characteristic() throws Exception { + DecoratorContext context = mock(DecoratorContext.class); + when(context.getResource()).thenReturn(new Project("foo")); + + DefaultCharacteristic rootCharacteristic = new DefaultCharacteristic().setKey("EFFICIENCY"); + DefaultCharacteristic characteristic = new DefaultCharacteristic().setKey("MEMORY_EFFICIENCY").setParent(rootCharacteristic); + + decorator.saveCharacteristicMeasure(context, characteristic, 0.0, true); + verify(context, never()).saveMeasure(any(Measure.class)); + } + + @Test + public void not_save_technical_debt_for_file_if_zero() throws Exception { + DecoratorContext context = mock(DecoratorContext.class); + when(context.getResource()).thenReturn(new File("foo")); + + decorator.saveCharacteristicMeasure(context, null, 0.0, true); + verify(context, never()).saveMeasure(new Measure(CoreMetrics.TECHNICAL_DEBT)); + } + + private DefaultIssue createIssue(String ruleKey, String repositoryKey) { + return new DefaultIssue().setRuleKey(RuleKey.of(repositoryKey, ruleKey)); + } + + class IsCharacteristicMeasure extends ArgumentMatcher { + Metric metric = null; + Characteristic characteristic = null; + Double value = null; + + public IsCharacteristicMeasure(Metric metric, Characteristic characteristic, Double value) { + this.metric = metric; + this.characteristic = characteristic; + this.value = value; + } + + @Override + public boolean matches(Object o) { + if (!(o instanceof Measure)) { + return false; + } + Measure m = (Measure) o; + return ObjectUtils.equals(metric, m.getMetric()) && + ObjectUtils.equals(characteristic, m.getCharacteristic()) && + ObjectUtils.equals(value, m.getValue()); + } + + @Override + public void describeTo(Description description) { + description.appendText(new StringBuilder() + .append("value=").append(value).append(",") + .append("characteristic=").append(characteristic.key()).append(",") + .append("metric=").append(metric.getKey()).toString()) + ; + } + } + + class IsRuleMeasure extends ArgumentMatcher { + Metric metric = null; + RuleKey ruleKey = null; + Double value = null; + + public IsRuleMeasure(Metric metric, RuleKey ruleKey, Double value) { + this.metric = metric; + this.ruleKey = ruleKey; + this.value = value; + } + + @Override + public boolean matches(Object o) { + if (!(o instanceof RuleMeasure)) { + return false; + } + RuleMeasure m = (RuleMeasure) o; + return ObjectUtils.equals(metric, m.getMetric()) && + ObjectUtils.equals(ruleKey.repository(), m.getRule().getRepositoryKey()) && + ObjectUtils.equals(ruleKey.rule(), m.getRule().getKey()) && + ObjectUtils.equals(value, m.getValue()); + } + + @Override + public void describeTo(Description description) { + description.appendText(ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE)); + } + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/debt/NewDebtDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/debt/NewDebtDecoratorTest.java new file mode 100644 index 00000000000..a80a19feac1 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/debt/NewDebtDecoratorTest.java @@ -0,0 +1,375 @@ +/* + * 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.batch.debt; + +import org.apache.commons.lang.ObjectUtils; +import org.apache.commons.lang.time.DateUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.component.ResourcePerspectives; +import org.sonar.api.config.Settings; +import org.sonar.api.issue.Issuable; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.api.issue.internal.FieldDiffs; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.Metric; +import org.sonar.api.resources.Resource; +import org.sonar.api.test.IsMeasure; +import org.sonar.api.utils.Duration; +import org.sonar.batch.components.Period; +import org.sonar.batch.components.TimeMachineConfiguration; +import org.sonar.batch.debt.IssueChangelogDebtCalculator; +import org.sonar.batch.debt.NewDebtDecorator; + +import java.util.Date; + +import static com.google.common.collect.Lists.newArrayList; +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class NewDebtDecoratorTest { + + NewDebtDecorator decorator; + + @Mock + TimeMachineConfiguration timeMachineConfiguration; + + @Mock + Resource resource; + + @Mock + Issuable issuable; + + @Mock + DecoratorContext context; + + Date rightNow; + Date elevenDaysAgo; + Date tenDaysAgo; + Date nineDaysAgo; + Date fiveDaysAgo; + Date fourDaysAgo; + + static final int HOURS_IN_DAY = 8; + + static final Long ONE_DAY_IN_MINUTES = 1L * HOURS_IN_DAY * 60; + static final Long TWO_DAYS_IN_MINUTES = 2L * HOURS_IN_DAY * 60; + static final Long FIVE_DAYS_IN_MINUTES = 5L * HOURS_IN_DAY * 60; + + @Before + public void setup() { + Settings settings = new Settings(); + settings.setProperty(CoreProperties.HOURS_IN_DAY, HOURS_IN_DAY); + + ResourcePerspectives perspectives = mock(ResourcePerspectives.class); + when(perspectives.as(Issuable.class, resource)).thenReturn(issuable); + + rightNow = new Date(); + elevenDaysAgo = DateUtils.addDays(rightNow, -11); + tenDaysAgo = DateUtils.addDays(rightNow, -10); + nineDaysAgo = DateUtils.addDays(rightNow, -9); + fiveDaysAgo = DateUtils.addDays(rightNow, -5); + fourDaysAgo = DateUtils.addDays(rightNow, -4); + + when(timeMachineConfiguration.periods()).thenReturn(newArrayList(new Period(1, fiveDaysAgo), new Period(2, tenDaysAgo))); + + decorator = new NewDebtDecorator(perspectives, timeMachineConfiguration, new IssueChangelogDebtCalculator()); + } + + @Test + public void generates_metrics() throws Exception { + assertThat(decorator.generatesMetrics()).hasSize(1); + } + + @Test + public void execute_on_project() throws Exception { + assertThat(decorator.shouldExecuteOnProject(null)).isTrue(); + } + + @Test + public void save_on_one_issue_with_one_new_changelog() { + Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES)).setChanges( + newArrayList( + // changelog created at is null because it has just been created on the current analysis + new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(null) + ) + ); + when(issuable.issues()).thenReturn(newArrayList(issue)); + + decorator.decorate(resource, context); + + // remember : period1 is 5daysAgo, period2 is 10daysAgo + verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 1.0 * ONE_DAY_IN_MINUTES, 1.0 * ONE_DAY_IN_MINUTES))); + } + + @Test + public void save_on_one_issue_with_changelog() { + Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges( + newArrayList( + new FieldDiffs().setDiff("technicalDebt", TWO_DAYS_IN_MINUTES, FIVE_DAYS_IN_MINUTES).setCreationDate(null), + new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(fourDaysAgo) + ) + ); + when(issuable.issues()).thenReturn(newArrayList(issue)); + + decorator.decorate(resource, context); + + // remember : period1 is 5daysAgo, period2 is 10daysAgo + verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 4.0 * ONE_DAY_IN_MINUTES, 4.0 * ONE_DAY_IN_MINUTES))); + } + + @Test + public void save_on_one_issue_with_changelog_only_in_the_past() { + Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(ONE_DAY_IN_MINUTES)).setChanges( + newArrayList( + // Change before all periods + new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(elevenDaysAgo) + ) + ); + when(issuable.issues()).thenReturn(newArrayList(issue)); + + decorator.decorate(resource, context); + + // remember : period1 is 5daysAgo, period2 is 10daysAgo + verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 0.0))); + } + + @Test + public void save_on_one_issue_with_changelog_having_null_value() { + Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges( + newArrayList( + new FieldDiffs().setDiff("technicalDebt", null, FIVE_DAYS_IN_MINUTES).setCreationDate(null), + new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, null).setCreationDate(fourDaysAgo), + new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo) + ) + ); + when(issuable.issues()).thenReturn(newArrayList(issue)); + + decorator.decorate(resource, context); + + // remember : period1 is 5daysAgo, period2 is 10daysAgo + verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 4.0 * ONE_DAY_IN_MINUTES, 5.0 * ONE_DAY_IN_MINUTES))); + } + + @Test + public void save_on_one_issue_with_changelog_and_periods_have_no_dates() { + when(timeMachineConfiguration.periods()).thenReturn(newArrayList(new Period(1, null), new Period(2, null))); + + Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges( + newArrayList( + new FieldDiffs().setDiff("technicalDebt", null, FIVE_DAYS_IN_MINUTES).setCreationDate(null), + new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, null).setCreationDate(fourDaysAgo), + new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo) + ) + ); + when(issuable.issues()).thenReturn(newArrayList(issue)); + + decorator.decorate(resource, context); + + // remember : period1 is 5daysAgo, period2 is 10daysAgo + verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 5.0 * ONE_DAY_IN_MINUTES, 5.0 * ONE_DAY_IN_MINUTES))); + } + + @Test + public void save_on_one_issue_with_changelog_having_not_only_technical_debt_changes() { + Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges( + newArrayList( + new FieldDiffs() + .setDiff("actionPlan", "1.0", "1.1").setCreationDate(fourDaysAgo) + .setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(fourDaysAgo) + ) + ); + when(issuable.issues()).thenReturn(newArrayList(issue)); + + decorator.decorate(resource, context); + + // remember : period1 is 5daysAgo, period2 is 10daysAgo + verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 4.0 * ONE_DAY_IN_MINUTES, 4.0 * ONE_DAY_IN_MINUTES))); + } + + @Test + public void save_on_issues_with_changelog() { + Issue issue1 = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges( + newArrayList( + new FieldDiffs().setDiff("technicalDebt", TWO_DAYS_IN_MINUTES, FIVE_DAYS_IN_MINUTES).setCreationDate(rightNow), + new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(fourDaysAgo), + new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo) + ) + ); + Issue issue2 = new DefaultIssue().setKey("B").setCreationDate(tenDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES)).setChanges( + newArrayList( + new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(rightNow), + new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo) + ) + ); + when(issuable.issues()).thenReturn(newArrayList(issue1, issue2)); + + decorator.decorate(resource, context); + + // remember : period1 is 5daysAgo, period2 is 10daysAgo + verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 5.0 * ONE_DAY_IN_MINUTES, 7.0 * ONE_DAY_IN_MINUTES))); + } + + @Test + public void save_on_one_issue_without_changelog() { + when(issuable.issues()).thenReturn(newArrayList( + (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES))) + ); + + decorator.decorate(resource, context); + + // remember : period1 is 5daysAgo, period2 is 10daysAgo + verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 5.0 * ONE_DAY_IN_MINUTES))); + } + + @Test + public void save_on_one_issue_without_technical_debt_and_without_changelog() { + when(issuable.issues()).thenReturn(newArrayList( + (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(null)) + ); + + decorator.decorate(resource, context); + + // remember : period1 is 5daysAgo, period2 is 10daysAgo + verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 0.0))); + } + + @Test + public void save_on_one_issue_without_changelog_and_periods_have_no_dates() { + when(timeMachineConfiguration.periods()).thenReturn(newArrayList(new Period(1, null), new Period(2, null))); + + when(issuable.issues()).thenReturn(newArrayList( + (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES))) + ); + + decorator.decorate(resource, context); + + // remember : period1 is null, period2 is null + verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 5.0 * ONE_DAY_IN_MINUTES, 5.0 * ONE_DAY_IN_MINUTES))); + } + + @Test + public void save_on_issues_without_changelog() { + when(issuable.issues()).thenReturn(newArrayList( + (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)), + new DefaultIssue().setKey("B").setCreationDate(fiveDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES)) + )); + + decorator.decorate(resource, context); + + // remember : period1 is 5daysAgo, period2 is 10daysAgo + verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 7.0 * ONE_DAY_IN_MINUTES))); + } + + @Test + public void save_on_issues_with_changelog_and_issues_without_changelog() { + // issue1 and issue2 have changelog + Issue issue1 = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)).setChanges( + newArrayList( + new FieldDiffs().setDiff("technicalDebt", TWO_DAYS_IN_MINUTES, FIVE_DAYS_IN_MINUTES).setCreationDate(rightNow), + new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(fourDaysAgo), + new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo) + ) + ); + Issue issue2 = new DefaultIssue().setKey("B").setCreationDate(tenDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES)).setChanges( + newArrayList( + new FieldDiffs().setDiff("technicalDebt", ONE_DAY_IN_MINUTES, TWO_DAYS_IN_MINUTES).setCreationDate(rightNow), + new FieldDiffs().setDiff("technicalDebt", null, ONE_DAY_IN_MINUTES).setCreationDate(nineDaysAgo) + ) + ); + + // issue3 and issue4 have no changelog + Issue issue3 = new DefaultIssue().setKey("C").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)); + Issue issue4 = new DefaultIssue().setKey("D").setCreationDate(fiveDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES)); + when(issuable.issues()).thenReturn(newArrayList(issue1, issue2, issue3, issue4)); + + decorator.decorate(resource, context); + + // remember : period1 is 5daysAgo, period2 is 10daysAgo + verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 5.0 * ONE_DAY_IN_MINUTES, 14.0 * ONE_DAY_IN_MINUTES))); + } + + @Test + public void not_save_if_measure_already_computed() { + when(context.getMeasure(CoreMetrics.NEW_TECHNICAL_DEBT)).thenReturn(new Measure()); + when(issuable.issues()).thenReturn(newArrayList( + (Issue) new DefaultIssue().setKey("A").setCreationDate(nineDaysAgo).setDebt(Duration.create(FIVE_DAYS_IN_MINUTES)), + new DefaultIssue().setKey("B").setCreationDate(fiveDaysAgo).setDebt(Duration.create(TWO_DAYS_IN_MINUTES)) + )); + + decorator.decorate(resource, context); + + verify(context, never()).saveMeasure(argThat(new IsMeasure(CoreMetrics.NEW_TECHNICAL_DEBT))); + } + + /** + * SONAR-5059 + */ + @Test + public void not_return_negative_debt() { + Issue issue = new DefaultIssue().setKey("A").setCreationDate(tenDaysAgo).setDebt(Duration.create(ONE_DAY_IN_MINUTES)).setChanges( + newArrayList( + // changelog created at is null because it has just been created on the current analysis + new FieldDiffs().setDiff("technicalDebt", TWO_DAYS_IN_MINUTES, ONE_DAY_IN_MINUTES).setCreationDate(null) + ) + ); + when(issuable.issues()).thenReturn(newArrayList(issue)); + + decorator.decorate(resource, context); + + // remember : period1 is 5daysAgo, period2 is 10daysAgo + verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0, 0.0))); + } + + + class IsVariationMeasure extends ArgumentMatcher { + Metric metric = null; + Double var1 = null; + Double var2 = null; + + public IsVariationMeasure(Metric metric, Double var1, Double var2) { + this.metric = metric; + this.var1 = var1; + this.var2 = var2; + } + + public boolean matches(Object o) { + if (!(o instanceof Measure)) { + return false; + } + Measure m = (Measure) o; + return ObjectUtils.equals(metric, m.getMetric()) && + ObjectUtils.equals(var1, m.getVariation1()) && + ObjectUtils.equals(var2, m.getVariation2()); + } + } + +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/IssueExclusionsConfigurationTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/IssueExclusionsConfigurationTest.java deleted file mode 100644 index 79849ba1358..00000000000 --- a/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/IssueExclusionsConfigurationTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.batch.issue.ignore; - -import org.sonar.batch.issue.ignore.IssueExclusionsConfiguration; - -import org.junit.Test; -import static org.fest.assertions.Assertions.assertThat; - -public class IssueExclusionsConfigurationTest { - @Test - public void justForCoverage() { - assertThat(IssueExclusionsConfiguration.getPropertyDefinitions()).hasSize(4); - } -} diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializerTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializerTest.java index f478c284184..a0707867019 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializerTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueExclusionPatternInitializerTest.java @@ -20,14 +20,14 @@ package org.sonar.batch.issue.ignore.pattern; -import org.sonar.batch.issue.ignore.IssueExclusionsConfiguration; -import org.sonar.batch.issue.ignore.pattern.IssueExclusionPatternInitializer; import org.junit.Before; import org.junit.Test; import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.config.Settings; import org.sonar.api.utils.SonarException; +import org.sonar.core.config.IssueExclusionProperties; + import static org.fest.assertions.Assertions.assertThat; public class IssueExclusionPatternInitializerTest { @@ -38,7 +38,7 @@ public class IssueExclusionPatternInitializerTest { @Before public void init() { - settings = new Settings(new PropertyDefinitions(IssueExclusionsConfiguration.getPropertyDefinitions())); + settings = new Settings(new PropertyDefinitions(IssueExclusionProperties.all())); patternsInitializer = new IssueExclusionPatternInitializer(settings); } @@ -51,11 +51,11 @@ public class IssueExclusionPatternInitializerTest { @Test public void shouldHavePatternsBasedOnMulticriteriaPattern() { - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY, "1,2"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY + ".1." + IssueExclusionsConfiguration.RESOURCE_KEY, "org/foo/Bar.java"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY + ".1." + IssueExclusionsConfiguration.RULE_KEY, "*"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY + ".2." + IssueExclusionsConfiguration.RESOURCE_KEY, "org/foo/Hello.java"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY + ".2." + IssueExclusionsConfiguration.RULE_KEY, "checkstyle:MagicNumber"); + settings.setProperty("sonar.issue.ignore" + ".multicriteria", "1,2"); + settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".1." + "resourceKey", "org/foo/Bar.java"); + settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".1." + "ruleKey", "*"); + settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".2." + "resourceKey", "org/foo/Hello.java"); + settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".2." + "ruleKey", "checkstyle:MagicNumber"); patternsInitializer.initPatterns(); assertThat(patternsInitializer.hasConfiguredPatterns()).isTrue(); @@ -77,29 +77,29 @@ public class IssueExclusionPatternInitializerTest { @Test(expected = SonarException.class) public void shouldLogInvalidResourceKey() { - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY, "1"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY + ".1." + IssueExclusionsConfiguration.RESOURCE_KEY, ""); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY + ".1." + IssueExclusionsConfiguration.RULE_KEY, "*"); + settings.setProperty("sonar.issue.ignore" + ".multicriteria", "1"); + settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".1." + "resourceKey", ""); + settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".1." + "ruleKey", "*"); patternsInitializer.initPatterns(); } @Test(expected = SonarException.class) public void shouldLogInvalidRuleKey() { - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY, "1"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY + ".1." + IssueExclusionsConfiguration.RESOURCE_KEY, "*"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_EXCLUSION_KEY + ".1." + IssueExclusionsConfiguration.RULE_KEY, ""); + settings.setProperty("sonar.issue.ignore" + ".multicriteria", "1"); + settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".1." + "resourceKey", "*"); + settings.setProperty("sonar.issue.ignore" + ".multicriteria" + ".1." + "ruleKey", ""); patternsInitializer.initPatterns(); } @Test public void shouldReturnBlockPattern() { - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY, "1,2,3"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionsConfiguration.BEGIN_BLOCK_REGEXP, "// SONAR-OFF"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionsConfiguration.END_BLOCK_REGEXP, "// SONAR-ON"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY + ".2." + IssueExclusionsConfiguration.BEGIN_BLOCK_REGEXP, "// FOO-OFF"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY + ".2." + IssueExclusionsConfiguration.END_BLOCK_REGEXP, "// FOO-ON"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY + ".3." + IssueExclusionsConfiguration.BEGIN_BLOCK_REGEXP, "// IGNORE-TO-EOF"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY + ".3." + IssueExclusionsConfiguration.END_BLOCK_REGEXP, ""); + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY, "1,2,3"); + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionProperties.BEGIN_BLOCK_REGEXP, "// SONAR-OFF"); + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionProperties.END_BLOCK_REGEXP, "// SONAR-ON"); + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".2." + IssueExclusionProperties.BEGIN_BLOCK_REGEXP, "// FOO-OFF"); + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".2." + IssueExclusionProperties.END_BLOCK_REGEXP, "// FOO-ON"); + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".3." + IssueExclusionProperties.BEGIN_BLOCK_REGEXP, "// IGNORE-TO-EOF"); + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".3." + IssueExclusionProperties.END_BLOCK_REGEXP, ""); patternsInitializer.loadFileContentPatterns(); assertThat(patternsInitializer.hasConfiguredPatterns()).isTrue(); @@ -112,17 +112,17 @@ public class IssueExclusionPatternInitializerTest { @Test(expected = SonarException.class) public void shouldLogInvalidStartBlockPattern() { - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY, "1"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionsConfiguration.BEGIN_BLOCK_REGEXP, ""); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionsConfiguration.END_BLOCK_REGEXP, "// SONAR-ON"); + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY, "1"); + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionProperties.BEGIN_BLOCK_REGEXP, ""); + settings.setProperty(IssueExclusionProperties.PATTERNS_BLOCK_KEY + ".1." + IssueExclusionProperties.END_BLOCK_REGEXP, "// SONAR-ON"); patternsInitializer.loadFileContentPatterns(); } @Test public void shouldReturnAllFilePattern() { - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_ALLFILE_KEY, "1,2"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_ALLFILE_KEY + ".1." + IssueExclusionsConfiguration.FILE_REGEXP, "@SONAR-IGNORE-ALL"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_ALLFILE_KEY + ".2." + IssueExclusionsConfiguration.FILE_REGEXP, "//FOO-IGNORE-ALL"); + settings.setProperty(IssueExclusionProperties.PATTERNS_ALLFILE_KEY, "1,2"); + settings.setProperty(IssueExclusionProperties.PATTERNS_ALLFILE_KEY + ".1." + IssueExclusionProperties.FILE_REGEXP, "@SONAR-IGNORE-ALL"); + settings.setProperty(IssueExclusionProperties.PATTERNS_ALLFILE_KEY + ".2." + IssueExclusionProperties.FILE_REGEXP, "//FOO-IGNORE-ALL"); patternsInitializer.loadFileContentPatterns(); assertThat(patternsInitializer.hasConfiguredPatterns()).isTrue(); @@ -135,8 +135,8 @@ public class IssueExclusionPatternInitializerTest { @Test(expected = SonarException.class) public void shouldLogInvalidAllFilePattern() { - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_ALLFILE_KEY, "1"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_ALLFILE_KEY + ".1." + IssueExclusionsConfiguration.FILE_REGEXP, ""); + settings.setProperty(IssueExclusionProperties.PATTERNS_ALLFILE_KEY, "1"); + settings.setProperty(IssueExclusionProperties.PATTERNS_ALLFILE_KEY + ".1." + IssueExclusionProperties.FILE_REGEXP, ""); patternsInitializer.loadFileContentPatterns(); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializerTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializerTest.java index ed93e89c8e7..eae28d70c05 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializerTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/issue/ignore/pattern/IssueInclusionPatternInitializerTest.java @@ -20,13 +20,12 @@ package org.sonar.batch.issue.ignore.pattern; -import org.sonar.batch.issue.ignore.IssueExclusionsConfiguration; -import org.sonar.batch.issue.ignore.pattern.IssueInclusionPatternInitializer; - import org.junit.Before; import org.junit.Test; import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.config.Settings; +import org.sonar.core.config.IssueExclusionProperties; + import static org.fest.assertions.Assertions.assertThat; public class IssueInclusionPatternInitializerTest { @@ -37,7 +36,7 @@ public class IssueInclusionPatternInitializerTest { @Before public void init() { - settings = new Settings(new PropertyDefinitions(IssueExclusionsConfiguration.getPropertyDefinitions())); + settings = new Settings(new PropertyDefinitions(IssueExclusionProperties.all())); patternsInitializer = new IssueInclusionPatternInitializer(settings); } @@ -49,11 +48,11 @@ public class IssueInclusionPatternInitializerTest { @Test public void shouldHavePatternsBasedOnMulticriteriaPattern() { - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_INCLUSION_KEY, "1,2"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_INCLUSION_KEY + ".1." + IssueExclusionsConfiguration.RESOURCE_KEY, "org/foo/Bar.java"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_INCLUSION_KEY + ".1." + IssueExclusionsConfiguration.RULE_KEY, "*"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_INCLUSION_KEY + ".2." + IssueExclusionsConfiguration.RESOURCE_KEY, "org/foo/Hello.java"); - settings.setProperty(IssueExclusionsConfiguration.PATTERNS_MULTICRITERIA_INCLUSION_KEY + ".2." + IssueExclusionsConfiguration.RULE_KEY, "checkstyle:MagicNumber"); + settings.setProperty("sonar.issue.enforce" + ".multicriteria", "1,2"); + settings.setProperty("sonar.issue.enforce" + ".multicriteria" + ".1." + "resourceKey", "org/foo/Bar.java"); + settings.setProperty("sonar.issue.enforce" + ".multicriteria" + ".1." + "ruleKey", "*"); + settings.setProperty("sonar.issue.enforce" + ".multicriteria" + ".2." + "resourceKey", "org/foo/Hello.java"); + settings.setProperty("sonar.issue.enforce" + ".multicriteria" + ".2." + "ruleKey", "checkstyle:MagicNumber"); patternsInitializer.initPatterns(); assertThat(patternsInitializer.hasConfiguredPatterns()).isTrue(); diff --git a/sonar-batch/src/test/java/org/sonar/batch/maven/DefaultMavenPluginExecutorTest.java b/sonar-batch/src/test/java/org/sonar/batch/maven/DefaultMavenPluginExecutorTest.java new file mode 100644 index 00000000000..75897a2abb3 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/maven/DefaultMavenPluginExecutorTest.java @@ -0,0 +1,91 @@ +/* + * 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.batch.maven; + +import org.apache.maven.project.MavenProject; +import org.junit.Test; +import org.sonar.api.batch.maven.MavenPlugin; +import org.sonar.api.batch.maven.MavenPluginHandler; +import org.sonar.api.resources.Project; +import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem; + +import java.io.File; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyList; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +public class DefaultMavenPluginExecutorTest { + + @Test + public void plugin_version_should_be_optional() { + assertThat(DefaultMavenPluginExecutor.getGoal("group", "artifact", null, "goal"), is("group:artifact::goal")); + } + + @Test + public void test_plugin_version() { + assertThat(DefaultMavenPluginExecutor.getGoal("group", "artifact", "3.54", "goal"), is("group:artifact:3.54:goal")); + } + + @Test + public void should_ignore_non_maven_projects() { + DefaultMavenPluginExecutor executor = new DefaultMavenPluginExecutor(null, null, mock(MavenProjectConverter.class)) { + @Override + public void concreteExecute(MavenProject pom, String goal) { + pom.addCompileSourceRoot("src/java"); + } + }; + Project foo = new Project("foo"); + DefaultModuleFileSystem fs = mock(DefaultModuleFileSystem.class); + executor.execute(foo, fs, new AddSourceMavenPluginHandler()); + + verify(fs, never()).resetDirs(any(File.class), any(File.class), anyList(), anyList(), anyList()); + } + + static class AddSourceMavenPluginHandler implements MavenPluginHandler { + public String getGroupId() { + return "fake"; + } + + public String getArtifactId() { + return "fake"; + } + + public String getVersion() { + return "2.2"; + } + + public boolean isFixedVersion() { + return false; + } + + public String[] getGoals() { + return new String[]{"fake"}; + } + + public void configure(Project project, MavenPlugin plugin) { + } + } + +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/maven/MavenProjectBootstrapperTest.java b/sonar-batch/src/test/java/org/sonar/batch/maven/MavenProjectBootstrapperTest.java new file mode 100644 index 00000000000..f4f7ea66051 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/maven/MavenProjectBootstrapperTest.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.batch.maven; + +import org.apache.maven.execution.MavenSession; +import org.apache.maven.project.MavenProject; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.bootstrap.ProjectReactor; + +import java.util.Arrays; +import java.util.List; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class MavenProjectBootstrapperTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void bootstrap() throws Exception { + ProjectDefinition def = mock(ProjectDefinition.class); + MavenSession session = mock(MavenSession.class); + MavenProject rootProject = new MavenProject(); + rootProject.setExecutionRoot(true); + List projects = Arrays.asList(rootProject); + when(session.getSortedProjects()).thenReturn(projects); + + MavenProjectConverter pomConverter = mock(MavenProjectConverter.class); + when(pomConverter.configure(projects, rootProject)).thenReturn(def); + MavenProjectBootstrapper bootstrapper = new MavenProjectBootstrapper(session, pomConverter); + + ProjectReactor reactor = bootstrapper.bootstrap(); + + assertThat(reactor).isNotNull(); + verify(pomConverter).configure(projects, rootProject); + } + + @Test + public void should_fail_if_no_top_level_project() throws Exception { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Maven session does not declare a top level project"); + + MavenSession session = mock(MavenSession.class); + MavenProjectConverter pomConverter = new MavenProjectConverter(); + MavenProjectBootstrapper bootstrapper = new MavenProjectBootstrapper(session, pomConverter); + + bootstrapper.bootstrap(); + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/maven/MavenProjectConverterTest.java b/sonar-batch/src/test/java/org/sonar/batch/maven/MavenProjectConverterTest.java new file mode 100644 index 00000000000..be1fd2cfe3e --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/maven/MavenProjectConverterTest.java @@ -0,0 +1,245 @@ +/* + * 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.batch.maven; + +import org.apache.commons.io.FileUtils; +import org.apache.maven.model.Model; +import org.apache.maven.model.io.xpp3.MavenXpp3Reader; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; +import org.hamcrest.core.Is; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.test.TestUtils; + +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.Properties; + +import static org.fest.assertions.Assertions.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +public class MavenProjectConverterTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + MavenProjectConverter converter = new MavenProjectConverter(); + + /** + * See SONAR-2681 + */ + @Test + public void shouldThrowExceptionWhenUnableToDetermineProjectStructure() { + MavenProject root = new MavenProject(); + root.setFile(new File("/foo/pom.xml")); + root.getBuild().setDirectory("target"); + root.getModules().add("module/pom.xml"); + + try { + converter.configure(Arrays.asList(root), root); + fail(); + } catch (IllegalStateException e) { + assertThat(e.getMessage(), containsString("Advanced Reactor Options")); + } + } + + @Test + public void shouldConvertModules() throws IOException { + File basedir = temp.newFolder(); + + MavenProject root = newMavenProject("com.foo", "parent", "1.0-SNAPSHOT"); + root.setFile(new File(basedir, "pom.xml")); + root.getBuild().setDirectory("target"); + root.getBuild().setOutputDirectory("target/classes"); + root.getModules().add("module/pom.xml"); + MavenProject module = newMavenProject("com.foo", "moduleA", "1.0-SNAPSHOT"); + module.setFile(new File(basedir, "module/pom.xml")); + module.getBuild().setDirectory("target"); + module.getBuild().setOutputDirectory("target/classes"); + ProjectDefinition project = converter.configure(Arrays.asList(root, module), root); + + assertThat(project.getSubProjects().size(), is(1)); + } + + private MavenProject newMavenProject(String groupId, String artifactId, String version) { + Model model = new Model(); + model.setGroupId(groupId); + model.setArtifactId(artifactId); + model.setVersion(version); + return new MavenProject(model); + } + + @Test + public void shouldConvertProperties() { + MavenProject pom = new MavenProject(); + pom.setGroupId("foo"); + pom.setArtifactId("bar"); + pom.setVersion("1.0.1"); + pom.setName("Test"); + pom.setDescription("just test"); + pom.setFile(new File("/foo/pom.xml")); + pom.getBuild().setDirectory("target"); + ProjectDefinition project = ProjectDefinition.create(); + converter.merge(pom, project); + + Properties properties = project.getProperties(); + assertThat(properties.getProperty(CoreProperties.PROJECT_KEY_PROPERTY), is("foo:bar")); + assertThat(properties.getProperty(CoreProperties.PROJECT_VERSION_PROPERTY), is("1.0.1")); + assertThat(properties.getProperty(CoreProperties.PROJECT_NAME_PROPERTY), is("Test")); + assertThat(properties.getProperty(CoreProperties.PROJECT_DESCRIPTION_PROPERTY), is("just test")); + } + + @Test + public void moduleNameShouldEqualArtifactId() throws Exception { + File rootDir = TestUtils.getResource("/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/"); + MavenProject parent = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/pom.xml", true); + MavenProject module1 = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module1/pom.xml", false); + MavenProject module2 = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module2/pom.xml", false); + + ProjectDefinition rootDef = converter.configure(Arrays.asList(parent, module1, module2), parent); + + assertThat(rootDef.getSubProjects().size(), Is.is(2)); + assertThat(rootDef.getKey(), Is.is("org.test:parent")); + assertNull(rootDef.getParent()); + assertThat(rootDef.getBaseDir(), is(rootDir)); + + ProjectDefinition module1Def = rootDef.getSubProjects().get(0); + assertThat(module1Def.getKey(), Is.is("org.test:module1")); + assertThat(module1Def.getParent(), Is.is(rootDef)); + assertThat(module1Def.getBaseDir(), Is.is(new File(rootDir, "module1"))); + assertThat(module1Def.getSubProjects().size(), Is.is(0)); + } + + @Test + public void moduleNameDifferentThanArtifactId() throws Exception { + File rootDir = TestUtils.getResource("/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/"); + MavenProject parent = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/pom.xml", true); + MavenProject module1 = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path1/pom.xml", false); + MavenProject module2 = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path2/pom.xml", false); + + ProjectDefinition rootDef = converter.configure(Arrays.asList(parent, module1, module2), parent); + + assertThat(rootDef.getSubProjects().size(), is(2)); + assertThat(rootDef.getKey(), is("org.test:parent")); + assertNull(rootDef.getParent()); + assertThat(rootDef.getBaseDir(), is(rootDir)); + + ProjectDefinition module1Def = rootDef.getSubProjects().get(0); + assertThat(module1Def.getKey(), Is.is("org.test:module1")); + assertThat(module1Def.getParent(), Is.is(rootDef)); + assertThat(module1Def.getBaseDir(), Is.is(new File(rootDir, "path1"))); + assertThat(module1Def.getSubProjects().size(), Is.is(0)); + } + + @Test + public void should_find_module_with_maven_project_file_naming_different_from_pom_xml() throws Exception { + File rootDir = TestUtils.getResource("/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/"); + MavenProject parent = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/pom.xml", true); + MavenProject module = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom_having_different_name.xml", false); + + ProjectDefinition rootDef = converter.configure(Arrays.asList(parent, module), parent); + + assertThat(rootDef.getSubProjects().size(), Is.is(1)); + assertThat(rootDef.getKey(), Is.is("org.test:parent")); + assertNull(rootDef.getParent()); + assertThat(rootDef.getBaseDir(), is(rootDir)); + + ProjectDefinition module1Def = rootDef.getSubProjects().get(0); + assertThat(module1Def.getKey(), Is.is("org.test:module")); + assertThat(module1Def.getParent(), Is.is(rootDef)); + assertThat(module1Def.getBaseDir(), Is.is(new File(rootDir, "module"))); + assertThat(module1Def.getSubProjects().size(), Is.is(0)); + } + + @Test + public void testSingleProjectWithoutModules() throws Exception { + File rootDir = TestUtils.getResource("/org/sonar/batch/maven/MavenProjectConverterTest/singleProjectWithoutModules/"); + MavenProject pom = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/singleProjectWithoutModules/pom.xml", true); + + ProjectDefinition rootDef = converter.configure(Arrays.asList(pom), pom); + + assertThat(rootDef.getKey(), is("org.test:parent")); + assertThat(rootDef.getSubProjects().size(), is(0)); + assertNull(rootDef.getParent()); + assertThat(rootDef.getBaseDir(), is(rootDir)); + } + + @Test + public void shouldConvertLinksToProperties() throws Exception { + MavenProject pom = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/projectWithLinks/pom.xml", true); + + ProjectDefinition rootDef = converter.configure(Arrays.asList(pom), pom); + + Properties props = rootDef.getProperties(); + assertThat(props.getProperty(CoreProperties.LINKS_HOME_PAGE)).isEqualTo("http://home.com"); + assertThat(props.getProperty(CoreProperties.LINKS_CI)).isEqualTo("http://ci.com"); + assertThat(props.getProperty(CoreProperties.LINKS_ISSUE_TRACKER)).isEqualTo("http://issues.com"); + assertThat(props.getProperty(CoreProperties.LINKS_SOURCES)).isEqualTo("http://sources.com"); + assertThat(props.getProperty(CoreProperties.LINKS_SOURCES_DEV)).isEqualTo("http://sources-dev.com"); + } + + @Test + public void shouldNotConvertLinksToPropertiesIfPropertyAlreadyDefined() throws Exception { + MavenProject pom = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/projectWithLinksAndProperties/pom.xml", true); + + ProjectDefinition rootDef = converter.configure(Arrays.asList(pom), pom); + + Properties props = rootDef.getProperties(); + + // Those properties have been fed by the POM elements , , ... + assertThat(props.getProperty(CoreProperties.LINKS_CI)).isEqualTo("http://ci.com"); + assertThat(props.getProperty(CoreProperties.LINKS_ISSUE_TRACKER)).isEqualTo("http://issues.com"); + assertThat(props.getProperty(CoreProperties.LINKS_SOURCES_DEV)).isEqualTo("http://sources-dev.com"); + + // ... but those ones have been overridden by in the POM + assertThat(props.getProperty(CoreProperties.LINKS_SOURCES)).isEqualTo("http://sources.com-OVERRIDEN-BY-PROPS"); + assertThat(props.getProperty(CoreProperties.LINKS_HOME_PAGE)).isEqualTo("http://home.com-OVERRIDEN-BY-PROPS"); + } + + @Test + public void shouldLoadSourceEncoding() throws Exception { + MavenProject pom = loadPom("/org/sonar/batch/maven/MavenProjectConverterTest/sourceEncoding/pom.xml", true); + + ProjectDefinition rootDef = converter.configure(Arrays.asList(pom), pom); + + assertThat(rootDef.getProperties().getProperty(CoreProperties.ENCODING_PROPERTY)).isEqualTo("Shift_JIS"); + } + + private MavenProject loadPom(String pomPath, boolean isRoot) throws URISyntaxException, IOException, XmlPullParserException { + File pomFile = new File(getClass().getResource(pomPath).toURI()); + Model model = new MavenXpp3Reader().read(new StringReader(FileUtils.readFileToString(pomFile))); + MavenProject pom = new MavenProject(model); + pom.setFile(pomFile); + pom.getBuild().setDirectory("target"); + pom.setExecutionRoot(isRoot); + return pom; + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/maven/SonarMavenProjectBuilderTest.java b/sonar-batch/src/test/java/org/sonar/batch/maven/SonarMavenProjectBuilderTest.java new file mode 100644 index 00000000000..16cbd06bc1e --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/maven/SonarMavenProjectBuilderTest.java @@ -0,0 +1,82 @@ +/* + * 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.batch.maven; + +import org.apache.maven.execution.MavenSession; +import org.apache.maven.project.MavenProject; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.sonar.api.batch.bootstrap.ProjectDefinition; + +import java.util.Arrays; +import java.util.List; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class SonarMavenProjectBuilderTest { + + @Test + public void testSimpleProject() { + MavenSession session = mock(MavenSession.class); + MavenProject rootProject = mock(MavenProject.class); + when(rootProject.isExecutionRoot()).thenReturn(true); + when(session.getSortedProjects()).thenReturn(Arrays.asList(rootProject)); + + MavenProjectConverter mavenProjectConverter = mock(MavenProjectConverter.class); + ProjectDefinition projectDefinition = ProjectDefinition.create(); + when(mavenProjectConverter.configure(any(List.class), any(MavenProject.class))).thenReturn(projectDefinition); + MavenProjectBootstrapper builder = new MavenProjectBootstrapper(session, mavenProjectConverter); + + assertThat(builder.bootstrap().getRoot()).isEqualTo(projectDefinition); + + ArgumentCaptor argument = ArgumentCaptor.forClass(List.class); + verify(mavenProjectConverter).configure(argument.capture(), eq(rootProject)); + assertThat(argument.getValue()).contains(rootProject); + } + + @Test + public void testMultimoduleProject() { + MavenSession session = mock(MavenSession.class); + MavenProject rootProject = mock(MavenProject.class); + MavenProject module1 = mock(MavenProject.class); + MavenProject module2 = mock(MavenProject.class); + when(rootProject.isExecutionRoot()).thenReturn(true); + when(module1.isExecutionRoot()).thenReturn(false); + when(module2.isExecutionRoot()).thenReturn(false); + when(session.getSortedProjects()).thenReturn(Arrays.asList(module1, module2, rootProject)); + + MavenProjectConverter mavenProjectConverter = mock(MavenProjectConverter.class); + ProjectDefinition projectDefinition = ProjectDefinition.create(); + when(mavenProjectConverter.configure(any(List.class), any(MavenProject.class))).thenReturn(projectDefinition); + MavenProjectBootstrapper builder = new MavenProjectBootstrapper(session, mavenProjectConverter); + + assertThat(builder.bootstrap().getRoot()).isEqualTo(projectDefinition); + + ArgumentCaptor argument = ArgumentCaptor.forClass(List.class); + verify(mavenProjectConverter).configure(argument.capture(), eq(rootProject)); + assertThat(argument.getValue()).contains(module1, module2, rootProject); + } + +} diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom.xml new file mode 100644 index 00000000000..7b49562c2e0 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom.xml @@ -0,0 +1,11 @@ + + 4.0.0 + + org.test + parent + 0.1-SNAPSHOT + + module + jar + \ No newline at end of file diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom_having_different_name.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom_having_different_name.xml new file mode 100644 index 00000000000..7b49562c2e0 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/module/pom_having_different_name.xml @@ -0,0 +1,11 @@ + + 4.0.0 + + org.test + parent + 0.1-SNAPSHOT + + module + jar + \ No newline at end of file diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/pom.xml new file mode 100644 index 00000000000..9d2244eb148 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/mavenProjectFileNameNotEqualsToPomXml/pom.xml @@ -0,0 +1,11 @@ + + 4.0.0 + org.test + parent + 0.1-SNAPSHOT + pom + + module + + \ No newline at end of file diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path1/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path1/pom.xml new file mode 100644 index 00000000000..470f2d1f6e5 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path1/pom.xml @@ -0,0 +1,11 @@ + + 4.0.0 + + org.test + parent + 0.1-SNAPSHOT + + module1 + jar + \ No newline at end of file diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path2/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path2/pom.xml new file mode 100644 index 00000000000..88101678e0f --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/path2/pom.xml @@ -0,0 +1,11 @@ + + 4.0.0 + + org.test + parent + 0.1-SNAPSHOT + + module2 + jar + \ No newline at end of file diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/pom.xml new file mode 100644 index 00000000000..afd92c0dbee --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameDifferentThanArtifactId/pom.xml @@ -0,0 +1,12 @@ + + 4.0.0 + org.test + parent + 0.1-SNAPSHOT + pom + + path1 + path2 + + \ No newline at end of file diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module1/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module1/pom.xml new file mode 100644 index 00000000000..470f2d1f6e5 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module1/pom.xml @@ -0,0 +1,11 @@ + + 4.0.0 + + org.test + parent + 0.1-SNAPSHOT + + module1 + jar + \ No newline at end of file diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module2/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module2/pom.xml new file mode 100644 index 00000000000..88101678e0f --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/module2/pom.xml @@ -0,0 +1,11 @@ + + 4.0.0 + + org.test + parent + 0.1-SNAPSHOT + + module2 + jar + \ No newline at end of file diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/pom.xml new file mode 100644 index 00000000000..cc73a43ec08 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/moduleNameShouldEqualArtifactId/pom.xml @@ -0,0 +1,12 @@ + + 4.0.0 + org.test + parent + 0.1-SNAPSHOT + pom + + module1 + module2 + + \ No newline at end of file diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/projectWithLinks/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/projectWithLinks/pom.xml new file mode 100644 index 00000000000..460e8967e5c --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/projectWithLinks/pom.xml @@ -0,0 +1,18 @@ + + 4.0.0 + org.test + parent + 0.1-SNAPSHOT + pom + http://home.com + + http://ci.com + + + http://issues.com + + + http://sources.com + http://sources-dev.com + + \ No newline at end of file diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/projectWithLinksAndProperties/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/projectWithLinksAndProperties/pom.xml new file mode 100644 index 00000000000..5b024e5c4a7 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/projectWithLinksAndProperties/pom.xml @@ -0,0 +1,27 @@ + + 4.0.0 + org.test + parent + 0.1-SNAPSHOT + pom + http://home.com + + http://ci.com + + + http://issues.com + + + http://sources.com + http://sources-dev.com + + + + + + http://home.com-OVERRIDEN-BY-PROPS + http://sources.com-OVERRIDEN-BY-PROPS + + + + \ No newline at end of file diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/singleProjectWithoutModules/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/singleProjectWithoutModules/pom.xml new file mode 100644 index 00000000000..ffd40530c5d --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/singleProjectWithoutModules/pom.xml @@ -0,0 +1,8 @@ + + 4.0.0 + org.test + parent + 0.1-SNAPSHOT + pom + \ No newline at end of file diff --git a/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/sourceEncoding/pom.xml b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/sourceEncoding/pom.xml new file mode 100644 index 00000000000..01bf73cfa9e --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/maven/MavenProjectConverterTest/sourceEncoding/pom.xml @@ -0,0 +1,11 @@ + + 4.0.0 + org.test + parent + 0.1-SNAPSHOT + pom + http://home.com + + Shift_JIS + + \ No newline at end of file diff --git a/sonar-core/src/main/java/org/sonar/core/config/CorePropertyDefinitions.java b/sonar-core/src/main/java/org/sonar/core/config/CorePropertyDefinitions.java new file mode 100644 index 00000000000..82bb38e79b6 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/config/CorePropertyDefinitions.java @@ -0,0 +1,117 @@ +/* + * 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.core.config; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import org.sonar.api.CoreProperties; +import org.sonar.api.PropertyType; +import org.sonar.api.config.PropertyDefinition; +import org.sonar.api.resources.Qualifiers; + +import java.util.List; + +public class CorePropertyDefinitions { + + private CorePropertyDefinitions() { + // only static stuff + } + + public static List all() { + List defs = Lists.newArrayList(); + defs.addAll(IssueExclusionProperties.all()); + defs.addAll(ExclusionProperties.all()); + defs.addAll(SecurityProperties.all()); + + defs.addAll(ImmutableList.of( + + // DEBT + PropertyDefinition.builder(CoreProperties.HOURS_IN_DAY) + .name("Number of working hours in a day") + .type(PropertyType.INTEGER) + .defaultValue("8") + .category(CoreProperties.CATEGORY_TECHNICAL_DEBT) + .deprecatedKey("sqale.hoursInDay") + .build(), + + // BATCH + + PropertyDefinition.builder(CoreProperties.CORE_VIOLATION_LOCALE_PROPERTY) + .defaultValue("en") + .name("Locale used for issue messages") + .description("Deprecated property. Keep default value for backward compatibility.") + .hidden() + .build(), + + PropertyDefinition.builder(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + 1) + .name("Period 1") + .description("Period used to compare measures and track new violations. Values are :
  • Number of days before " + + "analysis, for example 5.
  • A custom date. Format is yyyy-MM-dd, for example 2010-12-25
  • 'previous_analysis' to " + + "compare to previous analysis
  • 'previous_version' to compare to the previous version in the project history
" + + "

When specifying a number of days or a date, the snapshot selected for comparison is " + + " the first one available inside the corresponding time range.

" + + "

Changing this property only takes effect after subsequent project inspections.

") + .defaultValue(CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_1) + .category(CoreProperties.CATEGORY_GENERAL) + .subCategory(CoreProperties.SUBCATEGORY_DIFFERENTIAL_VIEWS) + .build(), + + PropertyDefinition.builder(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + 2) + .name("Period 2") + .description("See the property 'Period 1'") + .defaultValue(CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_2) + .category(CoreProperties.CATEGORY_GENERAL) + .subCategory(CoreProperties.SUBCATEGORY_DIFFERENTIAL_VIEWS) + .build(), + + PropertyDefinition.builder(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + 3) + .name("Period 3") + .description("See the property 'Period 1'") + .defaultValue(CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_3) + .category(CoreProperties.CATEGORY_GENERAL) + .subCategory(CoreProperties.SUBCATEGORY_DIFFERENTIAL_VIEWS) + .build(), + + PropertyDefinition.builder(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + 4) + .name("Period 4") + .description("Period used to compare measures and track new violations. This property is specific to the project. Values are : " + + "

  • Number of days before analysis, for example 5.
  • A custom date. Format is yyyy-MM-dd, " + + "for example 2010-12-25
  • 'previous_analysis' to compare to previous analysis
  • " + + "
  • 'previous_version' to compare to the previous version in the project history
  • A version, for example 1.2
" + + "

When specifying a number of days or a date, the snapshot selected for comparison is the first one available inside the corresponding time range.

" + + "

Changing this property only takes effect after subsequent project inspections.

") + .defaultValue(CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_4) + .onlyOnQualifiers(Qualifiers.PROJECT) + .category(CoreProperties.CATEGORY_GENERAL) + .subCategory(CoreProperties.SUBCATEGORY_DIFFERENTIAL_VIEWS) + .build(), + + PropertyDefinition.builder(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + 5) + .name("Period 5") + .description("See the property 'Period 4'") + .defaultValue(CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_5) + .onlyOnQualifiers(Qualifiers.PROJECT) + .category(CoreProperties.CATEGORY_GENERAL) + .subCategory(CoreProperties.SUBCATEGORY_DIFFERENTIAL_VIEWS) + .build() + )); + return defs; + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/config/ExclusionProperties.java b/sonar-core/src/main/java/org/sonar/core/config/ExclusionProperties.java new file mode 100644 index 00000000000..47756b864d6 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/config/ExclusionProperties.java @@ -0,0 +1,115 @@ +/* + * 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.core.config; + +import com.google.common.collect.ImmutableList; +import org.sonar.api.CoreProperties; +import org.sonar.api.PropertyType; +import org.sonar.api.config.PropertyDefinition; +import org.sonar.api.resources.Qualifiers; + +import java.util.List; + +public class ExclusionProperties { + + private ExclusionProperties() { + // only static stuff + } + + public static List all() { + return ImmutableList.of( + + // COVERAGE + PropertyDefinition.builder("sonar.coverage.exclusions") + .category(CoreProperties.CATEGORY_EXCLUSIONS) + .subCategory(CoreProperties.SUBCATEGORY_COVERAGE_EXCLUSIONS) + .type(PropertyType.STRING) + .multiValues(true) + .onQualifiers(Qualifiers.PROJECT, Qualifiers.MODULE) + .build(), + + // FILES + PropertyDefinition.builder(CoreProperties.PROJECT_INCLUSIONS_PROPERTY) + .name("Source File Inclusions") + .multiValues(true) + .category(CoreProperties.CATEGORY_EXCLUSIONS) + .subCategory(CoreProperties.SUBCATEGORY_FILES_EXCLUSIONS) + .onQualifiers(Qualifiers.PROJECT) + .index(3) + .build(), + PropertyDefinition.builder(CoreProperties.PROJECT_TEST_INCLUSIONS_PROPERTY) + .name("Test File Inclusions") + .multiValues(true) + .category(CoreProperties.CATEGORY_EXCLUSIONS) + .subCategory(CoreProperties.SUBCATEGORY_FILES_EXCLUSIONS) + .onQualifiers(Qualifiers.PROJECT) + .index(5) + .build(), + PropertyDefinition.builder(CoreProperties.GLOBAL_EXCLUSIONS_PROPERTY) + .name("Global Source File Exclusions") + .multiValues(true) + .category(CoreProperties.CATEGORY_EXCLUSIONS) + .subCategory(CoreProperties.SUBCATEGORY_FILES_EXCLUSIONS) + .index(0) + .build(), + PropertyDefinition.builder(CoreProperties.GLOBAL_TEST_EXCLUSIONS_PROPERTY) + .name("Global Test File Exclusions") + .multiValues(true) + .category(CoreProperties.CATEGORY_EXCLUSIONS) + .subCategory(CoreProperties.SUBCATEGORY_FILES_EXCLUSIONS) + .index(1) + .build(), + PropertyDefinition.builder(CoreProperties.PROJECT_EXCLUSIONS_PROPERTY) + .name("Source File Exclusions") + .multiValues(true) + .category(CoreProperties.CATEGORY_EXCLUSIONS) + .subCategory(CoreProperties.SUBCATEGORY_FILES_EXCLUSIONS) + .onQualifiers(Qualifiers.PROJECT) + .index(2) + .build(), + PropertyDefinition.builder(CoreProperties.PROJECT_TEST_EXCLUSIONS_PROPERTY) + .name("Test File Exclusions") + .multiValues(true) + .category(CoreProperties.CATEGORY_EXCLUSIONS) + .subCategory(CoreProperties.SUBCATEGORY_FILES_EXCLUSIONS) + .onQualifiers(Qualifiers.PROJECT) + .index(4) + .build(), + PropertyDefinition.builder(CoreProperties.CORE_SKIPPED_MODULES_PROPERTY) + .name("Exclude Modules") + .description("Maven artifact ids of modules to exclude.") + .multiValues(true) + .category(CoreProperties.CATEGORY_EXCLUSIONS) + .subCategory(CoreProperties.SUBCATEGORY_FILES_EXCLUSIONS) + .onlyOnQualifiers(Qualifiers.PROJECT) + .index(0) + .build(), + PropertyDefinition.builder(CoreProperties.CORE_INCLUDED_MODULES_PROPERTY) + .name("Include Modules") + .description("Maven artifact ids of modules to include.") + .multiValues(true) + .category(CoreProperties.CATEGORY_EXCLUSIONS) + .subCategory(CoreProperties.SUBCATEGORY_FILES_EXCLUSIONS) + .onlyOnQualifiers(Qualifiers.PROJECT) + .index(1) + .build() + ); + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/config/IssueExclusionProperties.java b/sonar-core/src/main/java/org/sonar/core/config/IssueExclusionProperties.java new file mode 100644 index 00000000000..b98f1cab9be --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/config/IssueExclusionProperties.java @@ -0,0 +1,150 @@ +/* + * 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.core.config; + +import com.google.common.collect.ImmutableList; +import org.sonar.api.CoreProperties; +import org.sonar.api.PropertyType; +import org.sonar.api.config.PropertyDefinition; +import org.sonar.api.config.PropertyFieldDefinition; +import org.sonar.api.resources.Qualifiers; + +import java.util.List; + +public final class IssueExclusionProperties { + + public static final String SUB_CATEGORY_IGNORE_ISSUES = "issues"; + + public static final String EXCLUSION_KEY_PREFIX = "sonar.issue.ignore"; + public static final String INCLUSION_KEY_PREFIX = "sonar.issue.enforce"; + + public static final String MULTICRITERIA_SUFFIX = ".multicriteria"; + public static final String PATTERNS_MULTICRITERIA_EXCLUSION_KEY = EXCLUSION_KEY_PREFIX + MULTICRITERIA_SUFFIX; + public static final String PATTERNS_MULTICRITERIA_INCLUSION_KEY = INCLUSION_KEY_PREFIX + MULTICRITERIA_SUFFIX; + public static final String RESOURCE_KEY = "resourceKey"; + private static final String PROPERTY_FILE_PATH_PATTERN = "File Path Pattern"; + public static final String RULE_KEY = "ruleKey"; + private static final String PROPERTY_RULE_KEY_PATTERN = "Rule Key Pattern"; + private static final String PROPERTY_RULE_KEY_PATTERN_HELP = "
A rule key pattern consists of the rule repository name, followed by a colon, followed by a rule key " + + "or rule name fragment. For example:" + + "

  • squid:S1195
  • squid:*Naming*
"; + + public static final String BLOCK_SUFFIX = ".block"; + public static final String PATTERNS_BLOCK_KEY = EXCLUSION_KEY_PREFIX + BLOCK_SUFFIX; + public static final String BEGIN_BLOCK_REGEXP = "beginBlockRegexp"; + public static final String END_BLOCK_REGEXP = "endBlockRegexp"; + + public static final String ALLFILE_SUFFIX = ".allfile"; + public static final String PATTERNS_ALLFILE_KEY = EXCLUSION_KEY_PREFIX + ALLFILE_SUFFIX; + public static final String FILE_REGEXP = "fileRegexp"; + + public static final int LARGE_SIZE = 40; + + private IssueExclusionProperties() { + // only static + } + + public static List all() { + return ImmutableList.of( + PropertyDefinition.builder(PATTERNS_MULTICRITERIA_EXCLUSION_KEY) + .category(CoreProperties.CATEGORY_EXCLUSIONS) + .subCategory(SUB_CATEGORY_IGNORE_ISSUES) + .name("Ignore Issues on Multiple Criteria") + .description("Patterns to ignore issues on certain components and for certain coding rules." + PROPERTY_RULE_KEY_PATTERN_HELP) + .onQualifiers(Qualifiers.PROJECT) + .index(3) + .fields( + PropertyFieldDefinition.build(RULE_KEY) + .name(PROPERTY_RULE_KEY_PATTERN) + .description("Pattern to match rules which should be ignored.") + .type(PropertyType.STRING) + .indicativeSize(LARGE_SIZE) + .build(), + PropertyFieldDefinition.build(RESOURCE_KEY) + .name(PROPERTY_FILE_PATH_PATTERN) + .description("Pattern to match files which should be ignored.") + .type(PropertyType.STRING) + .indicativeSize(LARGE_SIZE) + .build() + ) + .build(), + PropertyDefinition.builder(PATTERNS_BLOCK_KEY) + .category(CoreProperties.CATEGORY_EXCLUSIONS) + .subCategory(SUB_CATEGORY_IGNORE_ISSUES) + .name("Ignore Issues in Blocks") + .description("Patterns to ignore all issues on specific blocks of code, while continuing to scan and mark issues on the remainder of the file.") + .onQualifiers(Qualifiers.PROJECT) + .index(2) + .fields( + PropertyFieldDefinition.build(BEGIN_BLOCK_REGEXP) + .name("Regular Expression for Start of Block") + .description("If this regular expression is found in a file, then following lines are ignored until end of block.") + .type(PropertyType.STRING) + .indicativeSize(LARGE_SIZE) + .build(), + PropertyFieldDefinition.build(END_BLOCK_REGEXP) + .name("Regular Expression for End of Block") + .description("If specified, this regular expression is used to determine the end of code blocks to ignore. If not, then block ends at the end of file.") + .type(PropertyType.STRING) + .indicativeSize(LARGE_SIZE) + .build() + ) + .build(), + PropertyDefinition.builder(PATTERNS_ALLFILE_KEY) + .category(CoreProperties.CATEGORY_EXCLUSIONS) + .subCategory(SUB_CATEGORY_IGNORE_ISSUES) + .name("Ignore Issues on Files") + .description("Patterns to ignore all issues on files that contain a block of code matching a given regular expression.") + .onQualifiers(Qualifiers.PROJECT) + .index(1) + .fields( + PropertyFieldDefinition.build(FILE_REGEXP) + .name("Regular Expression") + .description("If this regular expression is found in a file, then the whole file is ignored.") + .type(PropertyType.STRING) + .indicativeSize(LARGE_SIZE) + .build() + ) + .build(), + PropertyDefinition.builder(PATTERNS_MULTICRITERIA_INCLUSION_KEY) + .category(CoreProperties.CATEGORY_EXCLUSIONS) + .subCategory(SUB_CATEGORY_IGNORE_ISSUES) + .name("Restrict Scope of Coding Rules") + .description("Patterns to restrict the application of a rule to only certain components, ignoring all others." + PROPERTY_RULE_KEY_PATTERN_HELP) + .onQualifiers(Qualifiers.PROJECT) + .index(4) + .fields( + PropertyFieldDefinition.build(RULE_KEY) + .name(PROPERTY_RULE_KEY_PATTERN) + .description("Pattern used to match rules which should be restricted.") + .type(PropertyType.STRING) + .indicativeSize(LARGE_SIZE) + .build(), + PropertyFieldDefinition.build(RESOURCE_KEY) + .name(PROPERTY_FILE_PATH_PATTERN) + .description("Pattern used to match files to which the rules should be restricted.") + .type(PropertyType.STRING) + .indicativeSize(LARGE_SIZE) + .build() + ) + .build() + ); + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/config/SecurityProperties.java b/sonar-core/src/main/java/org/sonar/core/config/SecurityProperties.java new file mode 100644 index 00000000000..39157443966 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/config/SecurityProperties.java @@ -0,0 +1,80 @@ +/* + * 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.core.config; + +import com.google.common.collect.ImmutableList; +import org.sonar.api.CoreProperties; +import org.sonar.api.PropertyType; +import org.sonar.api.config.PropertyDefinition; +import org.sonar.api.resources.Qualifiers; + +import java.util.List; + +class SecurityProperties { + + private SecurityProperties() { + // only static stuff + } + + static List all() { + return ImmutableList.of( + PropertyDefinition.builder(CoreProperties.CORE_IMPORT_SOURCES_PROPERTY) + .defaultValue("" + CoreProperties.CORE_IMPORT_SOURCES_DEFAULT_VALUE) + .name("Import sources") + .description("Set to false if sources should not be imported and therefore not available in the Web UI (e.g. for security reasons).") + .type(PropertyType.BOOLEAN) + .onQualifiers(Qualifiers.PROJECT, Qualifiers.MODULE) + .category(CoreProperties.CATEGORY_SECURITY) + .build(), + + PropertyDefinition.builder(CoreProperties.CORE_ALLOW_USERS_TO_SIGNUP_PROPERTY) + .defaultValue("" + CoreProperties.CORE_ALLOW_USERS_TO_SIGNUP_DEAULT_VALUE) + .name("Allow users to sign up online") + .description("Users can sign up online.") + .type(PropertyType.BOOLEAN) + .category(CoreProperties.CATEGORY_SECURITY) + .build(), + + PropertyDefinition.builder(CoreProperties.CORE_DEFAULT_GROUP) + .defaultValue(CoreProperties.CORE_DEFAULT_GROUP_DEFAULT_VALUE) + .name("Default user group") + .description("Any new users will automatically join this group.") + .category(CoreProperties.CATEGORY_SECURITY) + .build(), + + PropertyDefinition.builder(CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY) + .defaultValue("" + CoreProperties.CORE_FORCE_AUTHENTICATION_DEFAULT_VALUE) + .name("Force user authentication") + .description("Forcing user authentication stops un-logged users to access SonarQube.") + .type(PropertyType.BOOLEAN) + .category(CoreProperties.CATEGORY_SECURITY) + .build(), + + PropertyDefinition.builder(CoreProperties.CORE_PREVENT_AUTOMATIC_PROJECT_CREATION) + .defaultValue(Boolean.toString(false)) + .name("Prevent automatic project creation") + .description("Set to true to prevent automatic project creation at first analysis and force project provisioning.") + .type(PropertyType.BOOLEAN) + .category(CoreProperties.CATEGORY_SECURITY) + .build() + ); + + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/config/CorePropertyDefinitionsTest.java b/sonar-core/src/test/java/org/sonar/core/config/CorePropertyDefinitionsTest.java new file mode 100644 index 00000000000..5f3b028baa7 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/config/CorePropertyDefinitionsTest.java @@ -0,0 +1,35 @@ +/* + * 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.core.config; + +import org.junit.Test; +import org.sonar.api.config.PropertyDefinition; + +import java.util.List; + +import static org.fest.assertions.Assertions.assertThat; + +public class CorePropertyDefinitionsTest { + @Test + public void all() { + List defs = CorePropertyDefinitions.all(); + assertThat(defs.size()).isGreaterThan(10); + } +} diff --git a/sonar-server/pom.xml b/sonar-server/pom.xml index 5dd76005a69..61d381de0ae 100644 --- a/sonar-server/pom.xml +++ b/sonar-server/pom.xml @@ -25,14 +25,8 @@ gson
- org.codehaus.sonar - sonar-batch - ${project.version} - - - org.codehaus.sonar - sonar-batch-maven-compat - ${project.version} + com.h2database + h2 org.codehaus.sonar @@ -49,6 +43,12 @@ org.codehaus.sonar sonar-deprecated + + + org.apache.maven + maven-project + + org.codehaus.sonar @@ -503,13 +503,6 @@ sonar-plugin provided - - org.codehaus.sonar.plugins - sonar-maven-batch-plugin - ${project.version} - sonar-plugin - provided - org.codehaus.sonar-plugins.java sonar-jacoco-plugin @@ -629,12 +622,6 @@ - - - com.h2database - h2 - - diff --git a/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java b/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java index 9932eda1aad..8095d1911b6 100644 --- a/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java +++ b/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java @@ -182,10 +182,6 @@ public class DefaultServerFileSystem implements ServerFileSystem, Startable { return new File(getDeployDir(), "plugins/index.txt"); } - public File getBootstrapIndex() { - return new File(getDeployDir(), "bootstrap/index.txt"); - } - /** * @deprecated since 4.1 */ diff --git a/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java b/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java index 73e14c71530..e7f9d4d9385 100644 --- a/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java +++ b/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java @@ -39,6 +39,7 @@ import org.sonar.api.utils.UriReader; import org.sonar.api.utils.internal.TempFolderCleaner; import org.sonar.core.component.SnapshotPerspectives; import org.sonar.core.component.db.ComponentDao; +import org.sonar.core.config.CorePropertyDefinitions; import org.sonar.core.config.Logback; import org.sonar.core.i18n.DefaultI18n; import org.sonar.core.i18n.GwtI18n; @@ -162,6 +163,7 @@ class ServerComponents { new TempFolderProvider(), System2.INSTANCE )); + components.addAll(CorePropertyDefinitions.all()); components.addAll(DatabaseMigrations.CLASSES); components.addAll(DaoUtils.getDaoClasses()); return components; @@ -191,7 +193,8 @@ class ServerComponents { // ws RestartHandler.class, - SystemWs.class + SystemWs.class, + BatchWs.class ); } @@ -405,7 +408,6 @@ class ServerComponents { startupContainer.addSingleton(JdbcDriverDeployer.class); startupContainer.addSingleton(RegisterDebtModel.class); startupContainer.addSingleton(GeneratePluginIndex.class); - startupContainer.addSingleton(GenerateBootstrapIndex.class); startupContainer.addSingleton(RegisterNewMeasureFilters.class); startupContainer.addSingleton(RegisterDashboards.class); startupContainer.addSingleton(RegisterPermissionTemplates.class); diff --git a/sonar-server/src/main/java/org/sonar/server/plugins/BatchResourcesServlet.java b/sonar-server/src/main/java/org/sonar/server/plugins/BatchResourcesServlet.java deleted file mode 100644 index f8daf5ae57e..00000000000 --- a/sonar-server/src/main/java/org/sonar/server/plugins/BatchResourcesServlet.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.plugins; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * This servlet allows to load libraries from directory "WEB-INF/lib" in order to provide them for batch-bootstrapper. - * Most probably this is not a best solution. - */ -public class BatchResourcesServlet extends HttpServlet { - - private static final Logger LOG = LoggerFactory.getLogger(BatchResourcesServlet.class); - private static final long serialVersionUID = -2100128371794649028L; - - @Override - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - String filename = filename(request); - if (StringUtils.isBlank(filename)) { - throw new IllegalArgumentException("Filename is missing."); - } else { - InputStream in = null; - OutputStream out = null; - try { - in = getServletContext().getResourceAsStream("/WEB-INF/lib/" + filename); - if (in == null) { - // TODO - } else { - response.setContentType("application/java-archive"); - out = response.getOutputStream(); - IOUtils.copy(in, out); - } - } catch (Exception e) { - LOG.error("Unable to load batch resource '" + filename + "'", e); - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - } finally { - IOUtils.closeQuietly(in); - IOUtils.closeQuietly(out); - } - } - } - - /** - * @return part of request URL after servlet path - */ - String filename(HttpServletRequest request) { - String filename = null; - if (StringUtils.endsWithIgnoreCase(request.getRequestURI(), "jar")) { - filename = StringUtils.substringAfterLast(request.getRequestURI(), "/"); - } - return filename; - } - -} diff --git a/sonar-server/src/main/java/org/sonar/server/plugins/BatchWs.java b/sonar-server/src/main/java/org/sonar/server/plugins/BatchWs.java new file mode 100644 index 00000000000..25c4cc91746 --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/plugins/BatchWs.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.plugins; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.filefilter.FileFilterUtils; +import org.apache.commons.io.filefilter.HiddenFileFilter; +import org.apache.commons.lang.CharUtils; +import org.apache.commons.lang.StringUtils; +import org.picocontainer.Startable; +import org.sonar.api.platform.Server; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.RequestHandler; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.home.cache.FileHashes; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; + +/** + * JAR files to be downloaded by sonar-runner. + */ +public class BatchWs implements WebService, Startable { + + private final Server server; + private String index; + private File batchDir; + + public BatchWs(Server server) { + this.server = server; + } + + @Override + public void start() { + StringBuilder sb = new StringBuilder(); + batchDir = new File(server.getRootDir(), "lib/batch"); + if (batchDir.exists()) { + Collection files = FileUtils.listFiles(batchDir, HiddenFileFilter.VISIBLE, FileFilterUtils.directoryFileFilter()); + for (File file : files) { + String filename = file.getName(); + if (StringUtils.endsWith(filename, ".jar")) { + sb.append(filename).append('|').append(new FileHashes().of(file)).append(CharUtils.LF); + } + } + } + this.index = sb.toString(); + } + + @Override + public void stop() { + // nothing to do + } + + @Override + public void define(Context context) { + NewController controller = context.createController("batch"); + controller.createAction("index") + .setInternal(true) + .setDescription("List the JAR files to be downloaded by source analyzer") + .setHandler(new RequestHandler() { + @Override + public void handle(Request request, Response response) { + index(response); + } + }); + controller.createAction("file") + .setInternal(true) + .setDescription("Download a JAR file required by source analyzer") + .setHandler(new RequestHandler() { + @Override + public void handle(Request request, Response response) { + file(request, response); + } + }).createParam("name", "Filename"); + + controller.done(); + } + + private void index(Response response) { + try { + response.stream().setMediaType("text/plain"); + IOUtils.write(index, response.stream().output()); + } catch (IOException e) { + throw new IllegalStateException("Fail to send batch index", e); + } + } + + private void file(Request request, Response response) { + String filename = request.mandatoryParam("name"); + try { + File input = new File(batchDir, filename); + if (!input.exists() || !FileUtils.directoryContains(batchDir, input)) { + throw new IllegalArgumentException("Bad filename: " + filename); + } + response.stream().setMediaType("application/java-archive"); + FileUtils.copyFile(input, response.stream().output()); + + } catch (IOException e) { + throw new IllegalStateException("Fail to send batch file " + filename, e); + } + } +} diff --git a/sonar-server/src/main/java/org/sonar/server/startup/GenerateBootstrapIndex.java b/sonar-server/src/main/java/org/sonar/server/startup/GenerateBootstrapIndex.java deleted file mode 100644 index 4b71498785a..00000000000 --- a/sonar-server/src/main/java/org/sonar/server/startup/GenerateBootstrapIndex.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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.startup; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Lists; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import org.apache.commons.io.filefilter.FileFilterUtils; -import org.apache.commons.io.filefilter.HiddenFileFilter; -import org.apache.commons.lang.CharUtils; -import org.apache.commons.lang.StringUtils; -import org.sonar.api.platform.Server; -import org.sonar.home.cache.FileHashes; -import org.sonar.server.platform.DefaultServerFileSystem; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.Collection; -import java.util.List; - -/** - * @since 3.5 - */ -public final class GenerateBootstrapIndex { - - // JARs starting with one of these prefixes are excluded from batch - private static final String[] IGNORE = {"jtds", "mysql", "postgresql", "jruby", "jfreechart", "eastwood", - "elasticsearch", "lucene"}; - - private static final String LIB_DIR = "/web/WEB-INF/lib"; - - private final Server server; - private final DefaultServerFileSystem fileSystem; - - public GenerateBootstrapIndex(DefaultServerFileSystem fileSystem, Server server) { - this.server = server; - this.fileSystem = fileSystem; - } - - public void start() throws IOException { - writeIndex(fileSystem.getBootstrapIndex()); - } - - void writeIndex(File indexFile) throws IOException { - FileUtils.forceMkdir(indexFile.getParentFile()); - FileWriter writer = new FileWriter(indexFile, false); - try { - File libDir = new File(server.getRootDir(), LIB_DIR); - // TODO hack for Medium tests - if (libDir.exists()) { - for (String path : getLibs(libDir)) { - writer.append(path); - File is = new File(libDir, path); - writer.append("|").append(new FileHashes().of(is)); - writer.append(CharUtils.LF); - } - writer.flush(); - } - - } finally { - IOUtils.closeQuietly(writer); - } - } - - @VisibleForTesting - static List getLibs(File libDir) { - List libs = Lists.newArrayList(); - - Collection files = FileUtils.listFiles(libDir, HiddenFileFilter.VISIBLE, FileFilterUtils.directoryFileFilter()); - for (File file : files) { - String path = file.getPath(); - if (StringUtils.endsWith(path, ".jar")) { - String filename = StringUtils.removeStart(path, libDir.getAbsolutePath() + "/"); - if (!isIgnored(filename)) { - libs.add(filename); - } - } - } - return libs; - } - - /** - * Dirty hack to disable downloading for certain files. - */ - static boolean isIgnored(String filename) { - for (String prefix : IGNORE) { - if (StringUtils.startsWith(filename, prefix)) { - return true; - } - } - return false; - } -} diff --git a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java index f480f7447ca..222fbe111db 100644 --- a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java +++ b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java @@ -35,7 +35,11 @@ import org.sonar.api.test.MutableTestPlan; import org.sonar.api.test.MutableTestable; import org.sonar.api.test.TestPlan; import org.sonar.api.test.Testable; -import org.sonar.api.web.*; +import org.sonar.api.web.Footer; +import org.sonar.api.web.NavigationSection; +import org.sonar.api.web.Page; +import org.sonar.api.web.RubyRailsWebservice; +import org.sonar.api.web.Widget; import org.sonar.core.component.SnapshotPerspectives; import org.sonar.core.measure.MeasureFilterEngine; import org.sonar.core.measure.MeasureFilterResult; @@ -46,8 +50,15 @@ import org.sonar.core.resource.ResourceIndexerDao; import org.sonar.core.resource.ResourceKeyUpdaterDao; import org.sonar.core.timemachine.Periods; import org.sonar.server.db.migrations.DatabaseMigrator; -import org.sonar.server.platform.*; -import org.sonar.server.plugins.*; +import org.sonar.server.platform.Platform; +import org.sonar.server.platform.ServerIdGenerator; +import org.sonar.server.platform.ServerSettings; +import org.sonar.server.platform.SettingsChangeNotifier; +import org.sonar.server.plugins.InstalledPluginReferentialFactory; +import org.sonar.server.plugins.PluginDownloader; +import org.sonar.server.plugins.ServerPluginJarsInstaller; +import org.sonar.server.plugins.ServerPluginRepository; +import org.sonar.server.plugins.UpdateCenterMatrixFactory; import org.sonar.server.rule.RuleRepositories; import org.sonar.server.source.CodeColorizers; import org.sonar.server.user.NewUserNotifier; @@ -57,7 +68,6 @@ import org.sonar.updatecenter.common.Version; import javax.annotation.CheckForNull; import javax.annotation.Nullable; -import java.io.IOException; import java.net.InetAddress; import java.sql.Connection; import java.util.Collection; @@ -369,10 +379,6 @@ public final class JRubyFacade { return Platform.getInstance().getContainer(); } - public String boostrapIndexPath() throws IOException { - return getContainer().getComponentByType(DefaultServerFileSystem.class).getBootstrapIndex().getCanonicalPath(); - } - // UPDATE PROJECT KEY ------------------------------------------------------------------ public void updateResourceKey(long projectId, String newKey) { get(ResourceKeyUpdaterDao.class).updateKey(projectId, newKey); diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/batch_bootstrap_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/batch_bootstrap_controller.rb index e8e7f802538..a12fb2fbc52 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/batch_bootstrap_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/batch_bootstrap_controller.rb @@ -90,11 +90,6 @@ class BatchBootstrapController < Api::ApiController render :json => JSON(json_properties) end - # GET /batch_bootstrap/index - def index - render :file => java_facade.boostrapIndexPath(), :layout => false - end - private def render_unauthorized(message, status=403) diff --git a/sonar-server/src/main/webapp/WEB-INF/config/routes.rb b/sonar-server/src/main/webapp/WEB-INF/config/routes.rb index 2a04f24fd55..72dbd1eb08b 100644 --- a/sonar-server/src/main/webapp/WEB-INF/config/routes.rb +++ b/sonar-server/src/main/webapp/WEB-INF/config/routes.rb @@ -14,6 +14,11 @@ ActionController::Routing::Routes.draw do |map| end map.connect 'api', :controller => 'api/java_ws', :action => 'redirect_to_ws_listing' + + # deprecated, sonar-runner should use batch/index and batch/file?name=xxx + map.connect 'batch_bootstrap/index', :controller => 'api/java_ws', :action => 'index', :wspath => 'batch', :wsaction => 'index' + map.connect 'batch/:name', :controller => 'api/java_ws', :action => 'index', :wspath => 'batch', :wsaction => 'file', :requirements => { :name => /.*/ } + map.connect 'api/metrics', :controller => 'api/metrics', :action => 'index', :conditions => { :method => :get } map.connect 'api/metrics/:id', :controller => 'api/metrics', :action => 'show', :conditions => { :method => :get } map.connect 'api/metrics/:id', :controller => 'api/metrics', :action => 'create', :conditions => { :method => :post } diff --git a/sonar-server/src/main/webapp/WEB-INF/web.xml b/sonar-server/src/main/webapp/WEB-INF/web.xml index 1dff5cbff4c..b4896f15c56 100644 --- a/sonar-server/src/main/webapp/WEB-INF/web.xml +++ b/sonar-server/src/main/webapp/WEB-INF/web.xml @@ -90,10 +90,6 @@ static org.sonar.server.plugins.StaticResourcesServlet - - batch - org.sonar.server.plugins.BatchResourcesServlet - chart @@ -107,10 +103,6 @@ static /static/* - - batch - /batch/* - diff --git a/sonar-server/src/test/java/org/sonar/server/plugins/BatchResourcesServletTest.java b/sonar-server/src/test/java/org/sonar/server/plugins/BatchResourcesServletTest.java deleted file mode 100644 index 0ad48616e6b..00000000000 --- a/sonar-server/src/test/java/org/sonar/server/plugins/BatchResourcesServletTest.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.plugins; - -import org.junit.Before; -import org.junit.Test; - -import javax.servlet.http.HttpServletRequest; - -import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class BatchResourcesServletTest { - private BatchResourcesServlet servlet; - private HttpServletRequest request; - - @Before - public void setUp() throws Exception { - servlet = new BatchResourcesServlet(); - request = mock(HttpServletRequest.class); - } - - @Test - public void test_filename() { - when(request.getContextPath()).thenReturn("sonar"); - when(request.getServletPath()).thenReturn("/batch"); - - when(request.getRequestURI()).thenReturn("/sonar/batch/sonar-core-2.6.jar"); - assertThat(servlet.filename(request)).isEqualTo("sonar-core-2.6.jar"); - - when(request.getRequestURI()).thenReturn("/sonar/batch/"); - assertThat(servlet.filename(request)).isNull(); - - when(request.getRequestURI()).thenReturn("/sonar/batch"); - assertThat(servlet.filename(request)).isNull(); - - when(request.getRequestURI()).thenReturn("/sonar/batch.html"); - assertThat(servlet.filename(request)).isNull(); - - when(request.getRequestURI()).thenReturn("/sonar/batch/index.html"); - assertThat(servlet.filename(request)).isNull(); - } - -} diff --git a/sonar-server/src/test/java/org/sonar/server/plugins/BatchWsTest.java b/sonar-server/src/test/java/org/sonar/server/plugins/BatchWsTest.java new file mode 100644 index 00000000000..d20d346180d --- /dev/null +++ b/sonar-server/src/test/java/org/sonar/server/plugins/BatchWsTest.java @@ -0,0 +1,108 @@ +/* + * 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.plugins; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.CharUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.platform.Server; +import org.sonar.api.server.ws.WsTester; + +import java.io.File; +import java.io.IOException; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BatchWsTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + Server server = mock(Server.class); + + @Before + public void prepare_fs() throws IOException { + File rootDir = temp.newFolder(); + when(server.getRootDir()).thenReturn(rootDir); + + File batchDir = new File(rootDir, "lib/batch"); + FileUtils.forceMkdir(batchDir); + FileUtils.writeStringToFile(new File(batchDir, "sonar-batch.jar"), "foo"); + } + + @Test + public void download_index() throws Exception { + BatchWs ws = new BatchWs(server); + ws.start(); + WsTester tester = new WsTester(ws); + + String index = tester.newRequest("index").execute().outputAsString(); + assertThat(index).isEqualTo("sonar-batch.jar|acbd18db4cc2f85cedef654fccc4a4d8" + CharUtils.LF); + + ws.stop(); + } + + @Test + public void download_file() throws Exception { + BatchWs ws = new BatchWs(server); + ws.start(); + WsTester tester = new WsTester(ws); + + String jar = tester.newRequest("file").setParam("name", "sonar-batch.jar").execute().outputAsString(); + assertThat(jar).isEqualTo("foo"); + } + + /** + * Do not allow to download files located outside the directory lib/batch, for example + * /etc/passwd + */ + @Test + public void check_location_of_file() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Bad filename: ../sonar-batch.jar"); + + BatchWs ws = new BatchWs(server); + ws.start(); + WsTester tester = new WsTester(ws); + + tester.newRequest("file").setParam("name", "../sonar-batch.jar").execute(); + } + + @Test + public void file_does_not_exist() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Bad filename: other.jar"); + + BatchWs ws = new BatchWs(server); + ws.start(); + WsTester tester = new WsTester(ws); + + tester.newRequest("file").setParam("name", "other.jar").execute(); + } +} diff --git a/sonar-server/src/test/java/org/sonar/server/startup/GenerateBootstrapIndexTest.java b/sonar-server/src/test/java/org/sonar/server/startup/GenerateBootstrapIndexTest.java deleted file mode 100644 index ca6f7c74e0e..00000000000 --- a/sonar-server/src/test/java/org/sonar/server/startup/GenerateBootstrapIndexTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.startup; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.io.File; -import java.io.IOException; - -import static org.fest.assertions.Assertions.assertThat; - -public class GenerateBootstrapIndexTest { - - @Rule - public TemporaryFolder rootDir = new TemporaryFolder(); - - @Before - public void setUp() throws Exception { - } - - @Test - public void determine_list_of_resources() throws IOException { - new File(rootDir.getRoot(), "/web/WEB-INF/lib").mkdirs(); - File webInf = new File(rootDir.getRoot(), "/web/WEB-INF"); - File lib = new File(rootDir.getRoot(), "/web/WEB-INF/lib"); - new File(webInf, "directory").mkdir(); - new File(lib, "sonar-core-2.6.jar").createNewFile(); - new File(lib, "treemap.rbr").createNewFile(); - new File(lib, "sonar-core-2.6.jar").createNewFile(); - - assertThat(GenerateBootstrapIndex.getLibs(lib)).hasSize(1); - assertThat(GenerateBootstrapIndex.getLibs(lib).get(0)).isEqualTo("sonar-core-2.6.jar"); - } - - @Test - public void ignore_some_jars() { - assertThat(GenerateBootstrapIndex.isIgnored("sonar-batch-2.6-SNAPSHOT.jar")).isFalse(); - assertThat(GenerateBootstrapIndex.isIgnored("mysql-connector-java-5.1.13.jar")).isTrue(); - assertThat(GenerateBootstrapIndex.isIgnored("postgresql-9.0-801.jdbc3.jar")).isTrue(); - assertThat(GenerateBootstrapIndex.isIgnored("jtds-1.2.4.jar")).isTrue(); - assertThat(GenerateBootstrapIndex.isIgnored("jfreechart-1.0.9.jar")).isTrue(); - assertThat(GenerateBootstrapIndex.isIgnored("eastwood-1.1.0.jar")).isTrue(); - assertThat(GenerateBootstrapIndex.isIgnored("jruby-complete-1.5.6.jar")).isTrue(); - assertThat(GenerateBootstrapIndex.isIgnored("jruby-rack-1.0.5.jar")).isTrue(); - assertThat(GenerateBootstrapIndex.isIgnored("elasticsearch-0.90.6.jar")).isTrue(); - assertThat(GenerateBootstrapIndex.isIgnored("lucene-core-4.5.1.jar")).isTrue(); - } - -}