From 691651f2ebbbf9b1fed8c05f772ae6ca69b370f3 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Tue, 29 Sep 2015 09:55:17 +0200 Subject: [PATCH] SONAR-6397 Create repository to load SCM data from report or batch --- ...ReportComputeEngineContainerPopulator.java | 10 +- .../server/computation/scm/Changeset.java | 133 +++++++++++++++ .../server/computation/scm/DbScmInfo.java | 108 ++++++++++++ .../server/computation/scm/ReportScmInfo.java | 151 +++++++++++++++++ .../sonar/server/computation/scm/ScmInfo.java | 49 ++++++ .../server/computation/scm/ScmInfoImpl.java | 95 +++++++++++ .../computation/scm/ScmInfoRepository.java | 39 +++++ .../scm/ScmInfoRepositoryImpl.java | 74 ++++++++ .../server/computation/scm/package-info.java | 24 +++ .../computation/source/LastCommitVisitor.java | 4 + .../server/computation/scm/ChangesetTest.java | 128 ++++++++++++++ .../server/computation/scm/DbScmInfoTest.java | 115 +++++++++++++ .../computation/scm/ReportScmInfoTest.java | 158 ++++++++++++++++++ .../computation/scm/ScmInfoImplTest.java | 136 +++++++++++++++ .../scm/ScmInfoRepositoryImplTest.java | 138 +++++++++++++++ .../scm/ScmInfoRepositoryRule.java | 53 ++++++ 16 files changed, 1411 insertions(+), 4 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/scm/Changeset.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/scm/DbScmInfo.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/scm/ReportScmInfo.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/scm/ScmInfo.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/scm/ScmInfoImpl.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/scm/ScmInfoRepository.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/scm/ScmInfoRepositoryImpl.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/scm/package-info.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/scm/ChangesetTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/scm/DbScmInfoTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/scm/ReportScmInfoTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/scm/ScmInfoImplTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/scm/ScmInfoRepositoryImplTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/scm/ScmInfoRepositoryRule.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/container/ReportComputeEngineContainerPopulator.java b/server/sonar-server/src/main/java/org/sonar/server/computation/container/ReportComputeEngineContainerPopulator.java index 654f601a5c4..b48d8113d58 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/container/ReportComputeEngineContainerPopulator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/container/ReportComputeEngineContainerPopulator.java @@ -23,9 +23,6 @@ import java.util.Arrays; import java.util.List; import org.sonar.core.issue.tracking.Tracker; import org.sonar.core.platform.ContainerPopulator; -import org.sonar.server.computation.queue.CeTask; -import org.sonar.server.computation.step.ComputationStepExecutor; -import org.sonar.server.computation.filesystem.ComputationTempFolderProvider; import org.sonar.server.computation.analysis.ReportAnalysisMetadataHolder; import org.sonar.server.computation.batch.BatchReportDirectoryHolderImpl; import org.sonar.server.computation.batch.BatchReportReaderImpl; @@ -34,6 +31,7 @@ import org.sonar.server.computation.component.ReportTreeRootHolderImpl; import org.sonar.server.computation.component.SettingsRepositoryImpl; import org.sonar.server.computation.debt.DebtModelHolderImpl; import org.sonar.server.computation.event.EventRepositoryImpl; +import org.sonar.server.computation.filesystem.ComputationTempFolderProvider; import org.sonar.server.computation.issue.BaseIssuesLoader; import org.sonar.server.computation.issue.CloseIssuesOnRemovedComponentsVisitor; import org.sonar.server.computation.issue.ComponentIssuesRepositoryImpl; @@ -76,11 +74,14 @@ import org.sonar.server.computation.qualitygate.EvaluationResultTextConverterImp import org.sonar.server.computation.qualitygate.QualityGateHolderImpl; import org.sonar.server.computation.qualitygate.QualityGateServiceImpl; import org.sonar.server.computation.qualityprofile.ActiveRulesHolderImpl; +import org.sonar.server.computation.queue.CeTask; +import org.sonar.server.computation.scm.ScmInfoRepositoryImpl; import org.sonar.server.computation.source.LastCommitVisitor; import org.sonar.server.computation.source.SourceLinesRepositoryImpl; import org.sonar.server.computation.sqale.SqaleMeasuresVisitor; -import org.sonar.server.computation.sqale.SqaleRatingSettings; import org.sonar.server.computation.sqale.SqaleNewMeasuresVisitor; +import org.sonar.server.computation.sqale.SqaleRatingSettings; +import org.sonar.server.computation.step.ComputationStepExecutor; import org.sonar.server.computation.step.ComputationSteps; import org.sonar.server.computation.step.ReportComputationSteps; import org.sonar.server.view.index.ViewIndex; @@ -134,6 +135,7 @@ public final class ReportComputeEngineContainerPopulator implements ContainerPop QualityGateServiceImpl.class, EvaluationResultTextConverterImpl.class, SourceLinesRepositoryImpl.class, + ScmInfoRepositoryImpl.class, // issues RuleCacheLoader.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/scm/Changeset.java b/server/sonar-server/src/main/java/org/sonar/server/computation/scm/Changeset.java new file mode 100644 index 00000000000..32bbe0e0340 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/scm/Changeset.java @@ -0,0 +1,133 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.computation.scm; + +import java.util.Objects; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +import static java.util.Objects.requireNonNull; + +@Immutable +public final class Changeset { + + private final String revision; + private final long date; + @CheckForNull + private final String author; + + private Changeset(Builder builder) { + this.revision = builder.revision; + this.author = builder.author; + this.date = builder.date; + } + + public static Builder newChangesetBuilder() { + return new Builder(); + } + + public static class Builder { + private String revision; + private Long date; + @CheckForNull + private String author; + + private Builder() { + // prevents direct instantiation + } + + public Builder setRevision(String revision) { + this.revision = checkRevision(revision); + return this; + } + + public Builder setDate(Long date) { + this.date = checkDate(date); + return this; + } + + public Builder setAuthor(@Nullable String author) { + this.author = author; + return this; + } + + public Changeset build() { + checkRevision(revision); + checkDate(date); + return new Changeset(this); + } + + private static String checkRevision(String revision){ + return requireNonNull(revision, "Revision cannot be null"); + } + + private static long checkDate(Long date){ + return requireNonNull(date, "Date cannot be null"); + } + + } + + public String getRevision() { + return revision; + } + + public long getDate() { + return date; + } + + @CheckForNull + public String getAuthor() { + return author; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Changeset changeset = (Changeset) o; + if (date != changeset.date) { + return false; + } + if (!revision.equals(changeset.revision)) { + return false; + } + return Objects.equals(author, changeset.author); + } + + @Override + public int hashCode() { + return Objects.hash(revision, author, date); + } + + @Override + public String toString() { + return "Changeset{" + + "revision='" + revision + '\'' + + ", author='" + author + '\'' + + ", date=" + date + + '}'; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/scm/DbScmInfo.java b/server/sonar-server/src/main/java/org/sonar/server/computation/scm/DbScmInfo.java new file mode 100644 index 00000000000..4e6bc51082f --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/scm/DbScmInfo.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.computation.scm; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import org.sonar.db.protobuf.DbFileSources; +import org.sonar.server.computation.component.Component; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Predicates.notNull; +import static com.google.common.collect.FluentIterable.from; +import static com.google.common.collect.Iterables.isEmpty; +import static java.lang.String.format; + +/** + * ScmInfo implementation based on the lines stored in DB + */ +@Immutable +class DbScmInfo implements ScmInfo { + + private final ScmInfo delegate; + + private DbScmInfo(ScmInfo delegate) { + this.delegate = delegate; + } + + static Optional create(Component component, Iterable lines) { + LineToChangeset lineToChangeset = new LineToChangeset(); + Iterable lineChangesets = from(lines) + .transform(lineToChangeset) + .filter(notNull()) + .toList(); + if (isEmpty(lineChangesets)) { + return Optional.absent(); + } + checkState(!lineToChangeset.isEncounteredLineWithoutScmInfo(), + format("Partial scm information stored in DB for component '%s'. Not all lines have SCM info. Can not proceed", component)); + return Optional.of(new DbScmInfo(new ScmInfoImpl(lineChangesets))); + } + + @Override + public Changeset getLatestChangeset() { + return delegate.getLatestChangeset(); + } + + @Override + public Changeset getChangesetForLine(int lineNumber) { + return delegate.getChangesetForLine(lineNumber); + } + + @Override + public boolean hasChangesetForLine(int lineNumber) { + return delegate.hasChangesetForLine(lineNumber); + } + + @Override + public Iterable getAllChangesets() { + return delegate.getAllChangesets(); + } + + /** + * Transforms {@link org.sonar.db.protobuf.DbFileSources.Line} into {@link Changeset} and keep a flag if it encountered + * at least one which did not have any SCM information. + */ + private static class LineToChangeset implements Function { + private boolean encounteredLineWithoutScmInfo = false; + + @Override + @Nullable + public Changeset apply(@Nonnull DbFileSources.Line input) { + if (input.hasScmRevision() || input.hasScmAuthor() || input.hasScmDate()) { + return Changeset.newChangesetBuilder() + .setRevision(input.getScmRevision()) + .setAuthor(input.getScmAuthor()) + .setDate(input.getScmDate()) + .build(); + } + + this.encounteredLineWithoutScmInfo = true; + return null; + } + + public boolean isEncounteredLineWithoutScmInfo() { + return encounteredLineWithoutScmInfo; + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/scm/ReportScmInfo.java b/server/sonar-server/src/main/java/org/sonar/server/computation/scm/ReportScmInfo.java new file mode 100644 index 00000000000..71f4baff12e --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/scm/ReportScmInfo.java @@ -0,0 +1,151 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.computation.scm; + +import com.google.common.base.Function; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import javax.annotation.Nonnull; +import javax.annotation.concurrent.Immutable; +import org.sonar.batch.protocol.output.BatchReport; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.FluentIterable.from; +import static java.util.Objects.requireNonNull; + +/** + * ScmInfo implementation based on the changeset information from the Report + */ +@Immutable +class ReportScmInfo implements ScmInfo { + private final ScmInfo delegate; + + ReportScmInfo(BatchReport.Changesets changesets) { + requireNonNull(changesets); + this.delegate = convertToScmInfo(changesets); + } + + private static ScmInfo convertToScmInfo(BatchReport.Changesets changesets) { + return new ScmInfoImpl( + from(new IntRangeIterable(changesets.getChangesetIndexByLineCount())) + .transform(new LineIndexToChangeset(changesets))); + } + + private static Changeset convert(BatchReport.Changesets.Changeset changeset) { + checkState(changeset.hasRevision(), "Changeset must have a revision"); + checkState(changeset.hasDate(), "Changeset must have a date"); + return Changeset.newChangesetBuilder() + .setRevision(changeset.getRevision()) + .setAuthor(changeset.hasAuthor() ? changeset.getAuthor() : null) + .setDate(changeset.getDate()) + .build(); + } + + @Override + public Changeset getLatestChangeset() { + return this.delegate.getLatestChangeset(); + } + + @Override + public Changeset getChangesetForLine(int lineNumber) { + return this.delegate.getChangesetForLine(lineNumber); + } + + @Override + public boolean hasChangesetForLine(int lineNumber) { + return delegate.hasChangesetForLine(lineNumber); + } + + @Override + public Iterable getAllChangesets() { + return this.delegate.getAllChangesets(); + } + + private static class LineIndexToChangeset implements Function { + private final BatchReport.Changesets changesets; + private final Map changeSetCache; + + public LineIndexToChangeset(BatchReport.Changesets changesets) { + this.changesets = changesets; + changeSetCache = new HashMap<>(changesets.getChangesetCount()); + } + + @Override + @Nonnull + public Changeset apply(@Nonnull Integer lineNumber) { + int changesetIndex = changesets.getChangesetIndexByLine(lineNumber - 1); + if (changeSetCache.containsKey(changesetIndex)) { + return changeSetCache.get(changesetIndex); + } + Changeset res = convert(changesets.getChangeset(changesetIndex)); + changeSetCache.put(changesetIndex, res); + return res; + } + } + + /** + * A simple Iterable which generate integer from 0 (included) to a specific value (excluded). + */ + private static final class IntRangeIterable implements Iterable { + private final int max; + + private IntRangeIterable(int max) { + checkArgument(max >= 0, "Max value must be >= 0"); + this.max = max; + } + + @Override + public Iterator iterator() { + return new IntRangeIterator(max); + } + } + + private static class IntRangeIterator implements Iterator { + private final int max; + + private int current = 0; + + public IntRangeIterator(int max) { + this.max = max; + } + + @Override + public boolean hasNext() { + return current < max; + } + + @Override + public Integer next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + current++; + return current; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Remove cannot be called"); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/scm/ScmInfo.java b/server/sonar-server/src/main/java/org/sonar/server/computation/scm/ScmInfo.java new file mode 100644 index 00000000000..a774de4d418 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/scm/ScmInfo.java @@ -0,0 +1,49 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.computation.scm; + +/** + * Represents the Scm information for a specific file. + */ +public interface ScmInfo { + + /** + * Get most recent ChangeSet of the file. Can never be null + */ + Changeset getLatestChangeset(); + + /** + * Get ChangeSet of the file for given line + * + * @throws IllegalArgumentException if there is no Changeset for the specified line, ie. the line number in invalid (either less than 1 or > linecount of the file) + */ + Changeset getChangesetForLine(int lineNumber); + + /** + * Check if there's a ChangeSet for given line + */ + boolean hasChangesetForLine(int lineNumber); + + /** + * Return all ChangeSets, in order, for all lines of the file + */ + Iterable getAllChangesets(); + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/scm/ScmInfoImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/scm/ScmInfoImpl.java new file mode 100644 index 00000000000..5e124e1020f --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/scm/ScmInfoImpl.java @@ -0,0 +1,95 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.computation.scm; + +import com.google.common.base.Predicate; +import java.util.Arrays; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.FluentIterable.from; +import static com.google.common.collect.Iterables.isEmpty; +import static java.util.Arrays.asList; + +@Immutable +public class ScmInfoImpl implements ScmInfo { + + @CheckForNull + private final Changeset latestChangeset; + private final Changeset[] lineChangesets; + + public ScmInfoImpl(Iterable lineChangesets) { + checkState(!isEmpty(lineChangesets), "A ScmInfo must have at least one Changeset and does not support any null one"); + this.lineChangesets = from(lineChangesets).filter(CheckNotNull.INSTANCE).toArray(Changeset.class); + this.latestChangeset = computeLatestChangeset(lineChangesets); + } + + private static Changeset computeLatestChangeset(Iterable lineChangesets) { + Changeset latestChangeset = null; + for (Changeset lineChangeset : lineChangesets) { + if (latestChangeset == null || lineChangeset.getDate() > latestChangeset.getDate()) { + latestChangeset = lineChangeset; + } + } + return latestChangeset; + } + + @Override + public Changeset getLatestChangeset() { + return latestChangeset; + } + + @Override + public Changeset getChangesetForLine(int lineNumber) { + checkArgument(lineNumber > 0 && lineNumber <= lineChangesets.length, String.format("There's no changeset on line %s", lineNumber)); + return lineChangesets[lineNumber - 1]; + } + + @Override + public boolean hasChangesetForLine(int lineNumber) { + return lineNumber <= lineChangesets.length; + } + + @Override + public Iterable getAllChangesets() { + return asList(lineChangesets); + } + + @Override + public String toString() { + return "ScmInfoImpl{" + + "latestChangeset=" + latestChangeset + + ", lineChangesets=" + Arrays.toString(lineChangesets) + + '}'; + } + + private enum CheckNotNull implements Predicate { + INSTANCE; + + @Override + public boolean apply(@Nullable Changeset input) { + checkState(input != null, "Null changeset are not allowed"); + return true; + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/scm/ScmInfoRepository.java b/server/sonar-server/src/main/java/org/sonar/server/computation/scm/ScmInfoRepository.java new file mode 100644 index 00000000000..defd8a9712b --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/scm/ScmInfoRepository.java @@ -0,0 +1,39 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.computation.scm; + +import com.google.common.base.Optional; +import org.sonar.server.computation.component.Component; + +/** + * Return SCM information of components. + * + * It will always search in the db if there's nothing in the report. + */ +public interface ScmInfoRepository { + + /** + * Returns Scm info for the specified component if there is any, first looking into the report, then into the database + * It there's nothing in the report and in the db (on first analysis for instance), then it return a {@link Optional#absent()}. + * + * @throws NullPointerException if argument is {@code null} + */ + Optional getScmInfo(Component component); +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/scm/ScmInfoRepositoryImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/scm/ScmInfoRepositoryImpl.java new file mode 100644 index 00000000000..7ac532af8aa --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/scm/ScmInfoRepositoryImpl.java @@ -0,0 +1,74 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.computation.scm; + +import com.google.common.base.Optional; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.batch.protocol.output.BatchReport; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.protobuf.DbFileSources; +import org.sonar.server.computation.batch.BatchReportReader; +import org.sonar.server.computation.component.Component; +import org.sonar.server.source.SourceService; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class ScmInfoRepositoryImpl implements ScmInfoRepository { + + private static final Logger LOGGER = Loggers.get(ScmInfoRepositoryImpl.class); + + private final BatchReportReader batchReportReader; + private final DbClient dbClient; + private final SourceService sourceService; + + public ScmInfoRepositoryImpl(BatchReportReader batchReportReader, DbClient dbClient, SourceService sourceService) { + this.batchReportReader = batchReportReader; + this.dbClient = dbClient; + this.sourceService = sourceService; + } + + @Override + public Optional getScmInfo(Component component) { + checkNotNull(component, "Component cannot be bull"); + BatchReport.Changesets changesets = batchReportReader.readChangesets(component.getReportAttributes().getRef()); + if (changesets == null) { + LOGGER.trace("Reading SCM info from db for file '{}'", component); + return getFromDb(component); + } else { + LOGGER.trace("Reading SCM info from report for file '{}'", component); + return Optional.of(new ReportScmInfo(changesets)); + } + } + + private Optional getFromDb(Component component){ + DbSession dbSession = dbClient.openSession(false); + try { + Optional> linesOpt = sourceService.getLines(dbSession, component.getUuid(), 1, Integer.MAX_VALUE); + if (linesOpt.isPresent()) { + return DbScmInfo.create(component, linesOpt.get()); + } + return Optional.absent(); + } finally { + dbClient.closeSession(dbSession); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/scm/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/computation/scm/package-info.java new file mode 100644 index 00000000000..ea71454195c --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/scm/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +@ParametersAreNonnullByDefault +package org.sonar.server.computation.scm; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/source/LastCommitVisitor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/source/LastCommitVisitor.java index dc01e3e258b..5c312104dfe 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/source/LastCommitVisitor.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/source/LastCommitVisitor.java @@ -78,6 +78,10 @@ public class LastCommitVisitor extends PathAwareVisitorAdapter baseMeasure = measureRepository.getBaseMeasure(file, lastCommitDateMetric); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/scm/ChangesetTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/scm/ChangesetTest.java new file mode 100644 index 00000000000..426e2b06549 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/scm/ChangesetTest.java @@ -0,0 +1,128 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.scm; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ChangesetTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void create_changeset() throws Exception { + Changeset underTest = Changeset.newChangesetBuilder() + .setAuthor("john") + .setDate(123456789L) + .setRevision("rev-1") + .build(); + + assertThat(underTest.getAuthor()).isEqualTo("john"); + assertThat(underTest.getDate()).isEqualTo(123456789L); + assertThat(underTest.getRevision()).isEqualTo("rev-1"); + } + + @Test + public void create_changeset_with_minimum_fields() throws Exception { + Changeset underTest = Changeset.newChangesetBuilder() + .setDate(123456789L) + .setRevision("rev-1") + .build(); + + assertThat(underTest.getAuthor()).isNull(); + assertThat(underTest.getDate()).isEqualTo(123456789L); + assertThat(underTest.getRevision()).isEqualTo("rev-1"); + } + + @Test + public void fail_with_NPE_when_setting_null_revision() throws Exception { + thrown.expect(NullPointerException.class); + thrown.expectMessage("Revision cannot be null"); + + Changeset.newChangesetBuilder().setRevision(null); + } + + @Test + public void fail_with_NPE_when_building_without_revision() throws Exception { + thrown.expect(NullPointerException.class); + thrown.expectMessage("Revision cannot be null"); + + Changeset.newChangesetBuilder() + .setAuthor("john") + .setDate(123456789L) + .build(); + } + + @Test + public void fail_with_NPE_when_setting_null_date() throws Exception { + thrown.expect(NullPointerException.class); + thrown.expectMessage("Date cannot be null"); + + Changeset.newChangesetBuilder().setDate(null); + } + + @Test + public void fail_with_NPE_when_building_without_date() throws Exception { + thrown.expect(NullPointerException.class); + thrown.expectMessage("Date cannot be null"); + + Changeset.newChangesetBuilder() + .setAuthor("john") + .setRevision("rev-1") + .build(); + } + + @Test + public void test_to_string() throws Exception { + Changeset underTest = Changeset.newChangesetBuilder() + .setAuthor("john") + .setDate(123456789L) + .setRevision("rev-1") + .build(); + + assertThat(underTest.toString()).isEqualTo("Changeset{revision='rev-1', author='john', date=123456789}"); + } + + @Test + public void test_equals_and_hash_code() throws Exception { + Changeset.Builder changesetBuilder = Changeset.newChangesetBuilder() + .setAuthor("john") + .setDate(123456789L) + .setRevision("rev-1"); + + Changeset changeset = changesetBuilder.build(); + Changeset sameChangeset = changesetBuilder.build(); + + Changeset anotherChangeset = Changeset.newChangesetBuilder() + .setAuthor("henry") + .setDate(1234567810L) + .setRevision("rev-2") + .build(); + + assertThat(changeset).isEqualTo(sameChangeset); + assertThat(changeset).isEqualTo(changeset); + assertThat(changeset).isNotEqualTo(anotherChangeset); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/scm/DbScmInfoTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/scm/DbScmInfoTest.java new file mode 100644 index 00000000000..8caf16fd75a --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/scm/DbScmInfoTest.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.server.computation.scm; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.db.protobuf.DbFileSources; +import org.sonar.server.computation.component.Component; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.guava.api.Assertions.assertThat; +import static org.sonar.server.computation.component.ReportComponent.builder; +import static org.sonar.server.source.index.FileSourceTesting.newFakeData; + +public class DbScmInfoTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + static final int FILE_REF = 1; + static final Component FILE = builder(Component.Type.FILE, FILE_REF).setKey("FILE_KEY").setUuid("FILE_UUID").build(); + + @Test + public void create_scm_info_with_some_changesets() throws Exception { + ScmInfo scmInfo = DbScmInfo.create(FILE, newFakeData(10).build().getLinesList()).get(); + + assertThat(scmInfo.getAllChangesets()).hasSize(10); + } + + @Test + public void return_changeset_for_a_given_line() throws Exception { + DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder(); + addLine(fileDataBuilder, 1, "john", 123456789L, "rev-1"); + addLine(fileDataBuilder, 2, "henry", 1234567810L, "rev-2"); + addLine(fileDataBuilder, 3, "henry", 1234567810L, "rev-2"); + addLine(fileDataBuilder, 4, "john", 123456789L, "rev-1"); + fileDataBuilder.build(); + + ScmInfo scmInfo = DbScmInfo.create(FILE, fileDataBuilder.getLinesList()).get(); + + assertThat(scmInfo.getAllChangesets()).hasSize(4); + + Changeset changeset = scmInfo.getChangesetForLine(4); + assertThat(changeset.getAuthor()).isEqualTo("john"); + assertThat(changeset.getDate()).isEqualTo(123456789L); + assertThat(changeset.getRevision()).isEqualTo("rev-1"); + } + + @Test + public void return_latest_changeset() throws Exception { + DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder(); + addLine(fileDataBuilder, 1, "john", 123456789L, "rev-1"); + // Older changeset + addLine(fileDataBuilder, 2, "henry", 1234567810L, "rev-2"); + addLine(fileDataBuilder, 3, "john", 123456789L, "rev-1"); + fileDataBuilder.build(); + + ScmInfo scmInfo = DbScmInfo.create(FILE, fileDataBuilder.getLinesList()).get(); + + Changeset latestChangeset = scmInfo.getLatestChangeset(); + assertThat(latestChangeset.getAuthor()).isEqualTo("henry"); + assertThat(latestChangeset.getDate()).isEqualTo(1234567810L); + assertThat(latestChangeset.getRevision()).isEqualTo("rev-2"); + } + + @Test + public void return_absent_dsm_info_when_no_changeset() throws Exception { + DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder(); + fileDataBuilder.addLinesBuilder().setLine(1); + + assertThat(DbScmInfo.create(FILE, fileDataBuilder.getLinesList())).isAbsent(); + } + + @Test + public void fail_with_ISE_when_changeset_has_no_field() throws Exception { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Partial scm information stored in DB for component 'ReportComponent{ref=1, key='FILE_KEY', type=FILE}'. " + + "Not all lines have SCM info. Can not proceed"); + + DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder(); + fileDataBuilder.addLinesBuilder().setLine(1); + fileDataBuilder.addLinesBuilder().setScmAuthor("John").setLine(2); + fileDataBuilder.build(); + + DbScmInfo.create(FILE, fileDataBuilder.getLinesList()).get().getAllChangesets(); + } + + private static void addLine(DbFileSources.Data.Builder dataBuilder, Integer line, String author, Long date, String revision) { + dataBuilder.addLinesBuilder() + .setLine(line) + .setScmAuthor(author) + .setScmDate(date) + .setScmRevision(revision); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/scm/ReportScmInfoTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/scm/ReportScmInfoTest.java new file mode 100644 index 00000000000..b455b33c684 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/scm/ReportScmInfoTest.java @@ -0,0 +1,158 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.scm; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.batch.protocol.output.BatchReport; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ReportScmInfoTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + static final int FILE_REF = 1; + + @Test + public void create_scm_info_with_some_changesets() throws Exception { + ScmInfo scmInfo = new ReportScmInfo(BatchReport.Changesets.newBuilder() + .setComponentRef(FILE_REF) + .addChangeset(BatchReport.Changesets.Changeset.newBuilder() + .setAuthor("john") + .setDate(123456789L) + .setRevision("rev-1") + .build()) + .addChangeset(BatchReport.Changesets.Changeset.newBuilder() + .setAuthor("henry") + .setDate(1234567810L) + .setRevision("rev-2") + .build()) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(1) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(0) + .build()); + + assertThat(scmInfo.getAllChangesets()).hasSize(4); + } + + @Test + public void return_changeset_for_a_given_line() throws Exception { + ScmInfo scmInfo = new ReportScmInfo(BatchReport.Changesets.newBuilder() + .setComponentRef(FILE_REF) + .addChangeset(BatchReport.Changesets.Changeset.newBuilder() + .setAuthor("john") + .setDate(123456789L) + .setRevision("rev-1") + .build()) + .addChangeset(BatchReport.Changesets.Changeset.newBuilder() + .setAuthor("henry") + .setDate(1234567810L) + .setRevision("rev-2") + .build()) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(1) + .addChangesetIndexByLine(1) + .addChangesetIndexByLine(0) + .build()); + + assertThat(scmInfo.getAllChangesets()).hasSize(4); + + Changeset changeset = scmInfo.getChangesetForLine(4); + assertThat(changeset.getAuthor()).isEqualTo("john"); + assertThat(changeset.getDate()).isEqualTo(123456789L); + assertThat(changeset.getRevision()).isEqualTo("rev-1"); + } + + @Test + public void return_latest_changeset() throws Exception { + ScmInfo scmInfo = new ReportScmInfo(BatchReport.Changesets.newBuilder() + .setComponentRef(FILE_REF) + .addChangeset(BatchReport.Changesets.Changeset.newBuilder() + .setAuthor("john") + .setDate(123456789L) + .setRevision("rev-1") + .build()) + // Older changeset + .addChangeset(BatchReport.Changesets.Changeset.newBuilder() + .setAuthor("henry") + .setDate(1234567810L) + .setRevision("rev-2") + .build()) + .addChangesetIndexByLine(0) + .addChangesetIndexByLine(1) + .addChangesetIndexByLine(0) + .build()); + + Changeset latestChangeset = scmInfo.getLatestChangeset(); + assertThat(latestChangeset.getAuthor()).isEqualTo("henry"); + assertThat(latestChangeset.getDate()).isEqualTo(1234567810L); + assertThat(latestChangeset.getRevision()).isEqualTo("rev-2"); + } + + @Test + public void fail_with_ISE_when_no_changeset() throws Exception { + thrown.expect(IllegalStateException.class); + + new ReportScmInfo(BatchReport.Changesets.newBuilder().build()); + } + + @Test + public void fail_with_NPE_when_report_is_null() throws Exception { + thrown.expect(NullPointerException.class); + + new ReportScmInfo(null); + } + + @Test + public void fail_with_ISE_when_changeset_has_no_revision() throws Exception { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Changeset must have a revision"); + + new ReportScmInfo(BatchReport.Changesets.newBuilder() + .setComponentRef(FILE_REF) + .addChangeset(BatchReport.Changesets.Changeset.newBuilder() + .setAuthor("john") + .setDate(123456789L) + .build()) + .addChangesetIndexByLine(0) + .build()); + } + + @Test + public void fail_with_ISE_when_changeset_has_no_date() throws Exception { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Changeset must have a date"); + + new ReportScmInfo(BatchReport.Changesets.newBuilder() + .setComponentRef(FILE_REF) + .addChangeset(BatchReport.Changesets.Changeset.newBuilder() + .setAuthor("john") + .setRevision("rev-1") + .build()) + .addChangesetIndexByLine(0) + .build()); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/scm/ScmInfoImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/scm/ScmInfoImplTest.java new file mode 100644 index 00000000000..096b8659acb --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/scm/ScmInfoImplTest.java @@ -0,0 +1,136 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.scm; + +import com.google.common.collect.Lists; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static com.google.common.collect.Lists.newArrayList; +import static org.assertj.core.api.Assertions.assertThat; + +public class ScmInfoImplTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + static final Changeset CHANGESET_1 = Changeset.newChangesetBuilder() + .setAuthor("john") + .setDate(123456789L) + .setRevision("rev-1") + .build(); + + static final Changeset CHANGESET_2 = Changeset.newChangesetBuilder() + .setAuthor("henry") + .setDate(1234567810L) + .setRevision("rev-2") + .build(); + + @Test + public void get_all_changesets() throws Exception { + ScmInfo scmInfo = createScmInfoWithTwoChangestOnFourLines(); + + assertThat(scmInfo.getAllChangesets()).containsOnly(CHANGESET_1, CHANGESET_2, CHANGESET_1, CHANGESET_1); + } + + @Test + public void get_latest_changeset() throws Exception { + ScmInfo scmInfo = createScmInfoWithTwoChangestOnFourLines(); + + assertThat(scmInfo.getLatestChangeset()).isEqualTo(CHANGESET_2); + } + + @Test + public void get_changeset_for_given_line() throws Exception { + ScmInfo scmInfo = createScmInfoWithTwoChangestOnFourLines(); + + assertThat(scmInfo.getChangesetForLine(1)).isEqualTo(CHANGESET_1); + assertThat(scmInfo.getChangesetForLine(2)).isEqualTo(CHANGESET_2); + assertThat(scmInfo.getChangesetForLine(3)).isEqualTo(CHANGESET_1); + assertThat(scmInfo.getChangesetForLine(4)).isEqualTo(CHANGESET_1); + } + + @Test + public void exists_for_given_line() throws Exception { + ScmInfo scmInfo = createScmInfoWithTwoChangestOnFourLines(); + + assertThat(scmInfo.hasChangesetForLine(1)).isTrue(); + assertThat(scmInfo.hasChangesetForLine(5)).isFalse(); + } + + @Test + public void fail_with_ISE_on_empty_changeset() throws Exception { + thrown.expect(IllegalStateException.class); + thrown.expectMessage("A ScmInfo must have at least one Changeset and does not support any null one"); + + new ScmInfoImpl(Lists.newArrayList()); + } + + @Test + public void fail_with_IAE_when_line_is_smaller_than_one() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("There's no changeset on line 0"); + + ScmInfo scmInfo = createScmInfoWithTwoChangestOnFourLines(); + scmInfo.getChangesetForLine(0); + } + + @Test + public void fail_with_IAE_when_line_is_bigger_than_changetset_size() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("There's no changeset on line 5"); + + ScmInfo scmInfo = createScmInfoWithTwoChangestOnFourLines(); + scmInfo.getChangesetForLine(5); + } + + @Test + public void test_to_string() throws Exception { + ScmInfo scmInfo = createScmInfoWithTwoChangestOnFourLines(); + + assertThat(scmInfo.toString()).isEqualTo("ScmInfoImpl{" + + "latestChangeset=Changeset{revision='rev-2', author='henry', date=1234567810}, " + + "lineChangesets=[" + + "Changeset{revision='rev-1', author='john', date=123456789}, " + + "Changeset{revision='rev-2', author='henry', date=1234567810}, " + + "Changeset{revision='rev-1', author='john', date=123456789}, " + + "Changeset{revision='rev-1', author='john', date=123456789}" + + "]}"); + } + + private static ScmInfo createScmInfoWithTwoChangestOnFourLines() { + Changeset changeset1 = Changeset.newChangesetBuilder() + .setAuthor("john") + .setDate(123456789L) + .setRevision("rev-1") + .build(); + // Latest changeset + Changeset changeset2 = Changeset.newChangesetBuilder() + .setAuthor("henry") + .setDate(1234567810L) + .setRevision("rev-2") + .build(); + + ScmInfo scmInfo = new ScmInfoImpl(newArrayList(changeset1, changeset2, changeset1, changeset1)); + return scmInfo; + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/scm/ScmInfoRepositoryImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/scm/ScmInfoRepositoryImplTest.java new file mode 100644 index 00000000000..04c0166a372 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/scm/ScmInfoRepositoryImplTest.java @@ -0,0 +1,138 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.scm; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.System2; +import org.sonar.batch.protocol.output.BatchReport; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.protobuf.DbFileSources; +import org.sonar.db.source.FileSourceDto; +import org.sonar.server.computation.batch.BatchReportReaderRule; +import org.sonar.server.computation.component.Component; +import org.sonar.server.source.SourceService; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.guava.api.Assertions.assertThat; +import static org.sonar.server.computation.component.ReportComponent.builder; + +public class ScmInfoRepositoryImplTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + static final int FILE_REF = 1; + static final Component FILE = builder(Component.Type.FILE, FILE_REF).setKey("FILE_KEY").setUuid("FILE_UUID").build(); + + @Rule + public BatchReportReaderRule reportReader = new BatchReportReaderRule(); + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + DbClient dbClient = dbTester.getDbClient(); + + ScmInfoRepositoryImpl underTest = new ScmInfoRepositoryImpl(reportReader, dbClient, new SourceService(dbClient, null)); + + @Test + public void read_from_report() throws Exception { + addChangesetInReport("john", 123456789L, "rev-1"); + + ScmInfo scmInfo = underTest.getScmInfo(FILE).get(); + assertThat(scmInfo.getAllChangesets()).hasSize(1); + } + + @Test + public void read_from_db() throws Exception { + addChangesetInDb("henry", 123456789L, "rev-1"); + + ScmInfo scmInfo = underTest.getScmInfo(FILE).get(); + assertThat(scmInfo.getAllChangesets()).hasSize(1); + } + + @Test + public void read_from_report_even_if_data_in_db_exists() throws Exception { + addChangesetInDb("henry", 123456789L, "rev-1"); + + addChangesetInReport("john", 1234567810L, "rev-2"); + + ScmInfo scmInfo = underTest.getScmInfo(FILE).get(); + + Changeset changeset = scmInfo.getChangesetForLine(1); + assertThat(changeset.getAuthor()).isEqualTo("john"); + assertThat(changeset.getDate()).isEqualTo(1234567810L); + assertThat(changeset.getRevision()).isEqualTo("rev-2"); + } + + @Test + public void return_nothing_when_no_data_in_report_and_db() throws Exception { + assertThat(underTest.getScmInfo(FILE)).isAbsent(); + } + + @Test + public void return_nothing_when_nothing_in_erpot_and_db_has_no_scm() throws Exception { + DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder(); + fileDataBuilder.addLinesBuilder() + .setLine(1); + dbTester.getDbClient().fileSourceDao().insert(new FileSourceDto() + .setFileUuid(FILE.getUuid()) + .setProjectUuid("PROJECT_UUID") + .setSourceData(fileDataBuilder.build())); + + assertThat(underTest.getScmInfo(FILE)).isAbsent(); + } + + @Test + public void fail_with_NPE_when_component_is_null() throws Exception { + thrown.expect(NullPointerException.class); + thrown.expectMessage("Component cannot be bull"); + + underTest.getScmInfo(null); + } + + private void addChangesetInDb(String author, Long date, String revision) { + DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder(); + fileDataBuilder.addLinesBuilder() + .setLine(1) + .setScmAuthor(author) + .setScmDate(date) + .setScmRevision(revision); + dbTester.getDbClient().fileSourceDao().insert(new FileSourceDto() + .setFileUuid(FILE.getUuid()) + .setProjectUuid("PROJECT_UUID") + .setSourceData(fileDataBuilder.build())); + } + + private void addChangesetInReport(String author, Long date, String revision) { + reportReader.putChangesets(BatchReport.Changesets.newBuilder() + .setComponentRef(FILE_REF) + .addChangeset(BatchReport.Changesets.Changeset.newBuilder() + .setAuthor(author) + .setDate(date) + .setRevision(revision) + .build()) + .addChangesetIndexByLine(0) + .build()); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/scm/ScmInfoRepositoryRule.java b/server/sonar-server/src/test/java/org/sonar/server/computation/scm/ScmInfoRepositoryRule.java new file mode 100644 index 00000000000..678a3602304 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/scm/ScmInfoRepositoryRule.java @@ -0,0 +1,53 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.computation.scm; + +import com.google.common.base.Optional; +import org.junit.rules.ExternalResource; +import org.sonar.server.computation.component.Component; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class ScmInfoRepositoryRule extends ExternalResource implements ScmInfoRepository { + + private Map scmInfoByFileRef = new HashMap<>(); + + @Override + protected void after() { + scmInfoByFileRef.clear(); + } + + @Override + public Optional getScmInfo(Component component) { + checkNotNull(component, "Component cannot be bull"); + ScmInfo scmInfo = scmInfoByFileRef.get(component.getReportAttributes().getRef()); + return Optional.fromNullable(scmInfo); + } + + public ScmInfoRepositoryRule setScmInfo(int fileRef, Changeset... changesetList) { + scmInfoByFileRef.put(fileRef, new ScmInfoImpl(Arrays.asList(changesetList))); + return this; + } +} -- 2.39.5