compileOnly 'com.google.code.findbugs:jsr305'
+ compile project(':server:sonar-db-dao')
+
testCompile 'com.google.code.findbugs:jsr305'
testCompile 'com.tngtech.java:junit-dataprovider'
testCompile 'org.apache.logging.log4j:log4j-api'
testFixturesApi testFixtures(project(':server:sonar-ce-task'))
testFixturesCompileOnly 'com.google.code.findbugs:jsr305'
+
}
import org.sonar.ce.task.projectanalysis.issue.EffortAggregator;
import org.sonar.ce.task.projectanalysis.issue.IntegrateIssuesVisitor;
import org.sonar.ce.task.projectanalysis.issue.IssueAssigner;
-import org.sonar.ce.task.projectanalysis.issue.IssueCache;
+import org.sonar.ce.task.projectanalysis.issue.ProtoIssueCache;
import org.sonar.ce.task.projectanalysis.issue.IssueCounter;
import org.sonar.ce.task.projectanalysis.issue.IssueCreationDateCalculator;
import org.sonar.ce.task.projectanalysis.issue.IssueLifecycle;
RuleRepositoryImpl.class,
ScmAccountToUserLoader.class,
ScmAccountToUser.class,
- IssueCache.class,
+ ProtoIssueCache.class,
DefaultAssignee.class,
IssueVisitors.class,
IssueLifecycle.class,
import org.sonar.ce.task.projectanalysis.component.Component;
import org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit;
import org.sonar.ce.task.projectanalysis.component.TypeAwareVisitorAdapter;
-import org.sonar.ce.task.projectanalysis.util.cache.DiskCache;
+import org.sonar.ce.task.projectanalysis.util.cache.DiskCache.CacheAppender;
import org.sonar.core.issue.DefaultIssue;
import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER;
private final ComponentIssuesLoader issuesLoader;
private final ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues;
- private final IssueCache issueCache;
+ private final ProtoIssueCache protoIssueCache;
private final IssueLifecycle issueLifecycle;
- public CloseIssuesOnRemovedComponentsVisitor(ComponentIssuesLoader issuesLoader, ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues, IssueCache issueCache,
+ public CloseIssuesOnRemovedComponentsVisitor(ComponentIssuesLoader issuesLoader, ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues, ProtoIssueCache protoIssueCache,
IssueLifecycle issueLifecycle) {
super(CrawlerDepthLimit.PROJECT, POST_ORDER);
this.issuesLoader = issuesLoader;
this.componentsWithUnprocessedIssues = componentsWithUnprocessedIssues;
- this.issueCache = issueCache;
+ this.protoIssueCache = protoIssueCache;
this.issueLifecycle = issueLifecycle;
}
}
private void closeIssuesForDeletedComponentUuids(Set<String> deletedComponentUuids) {
- try (DiskCache<DefaultIssue>.DiskAppender cacheAppender = issueCache.newAppender()) {
+ try (CacheAppender<DefaultIssue> cacheAppender = protoIssueCache.newAppender()) {
for (String deletedComponentUuid : deletedComponentUuids) {
List<DefaultIssue> issues = issuesLoader.loadOpenIssues(deletedComponentUuid);
for (DefaultIssue issue : issues) {
import org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit;
import org.sonar.ce.task.projectanalysis.component.ReferenceBranchComponentUuids;
import org.sonar.ce.task.projectanalysis.component.TypeAwareVisitorAdapter;
-import org.sonar.ce.task.projectanalysis.util.cache.DiskCache;
+import org.sonar.ce.task.projectanalysis.util.cache.DiskCache.CacheAppender;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.util.stream.MoreCollectors;
public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter {
- private final IssueCache issueCache;
+ private final ProtoIssueCache protoIssueCache;
private final IssueLifecycle issueLifecycle;
private final IssueVisitors issueVisitors;
private final IssueTrackingDelegator issueTracking;
private final SiblingsIssueMerger issueStatusCopier;
private final ReferenceBranchComponentUuids referenceBranchComponentUuids;
- public IntegrateIssuesVisitor(IssueCache issueCache, IssueLifecycle issueLifecycle, IssueVisitors issueVisitors, IssueTrackingDelegator issueTracking,
+ public IntegrateIssuesVisitor(ProtoIssueCache protoIssueCache, IssueLifecycle issueLifecycle, IssueVisitors issueVisitors, IssueTrackingDelegator issueTracking,
SiblingsIssueMerger issueStatusCopier, ReferenceBranchComponentUuids referenceBranchComponentUuids) {
super(CrawlerDepthLimit.FILE, POST_ORDER);
- this.issueCache = issueCache;
+ this.protoIssueCache = protoIssueCache;
this.issueLifecycle = issueLifecycle;
this.issueVisitors = issueVisitors;
this.issueTracking = issueTracking;
@Override
public void visitAny(Component component) {
- try (DiskCache<DefaultIssue>.DiskAppender cacheAppender = issueCache.newAppender()) {
+ try (CacheAppender<DefaultIssue> cacheAppender = protoIssueCache.newAppender()) {
issueVisitors.beforeComponent(component);
TrackingResult tracking = issueTracking.track(component);
fillNewOpenIssues(component, tracking.newIssues(), cacheAppender);
}
}
- private void fillNewOpenIssues(Component component, Stream<DefaultIssue> newIssues, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
+ private void fillNewOpenIssues(Component component, Stream<DefaultIssue> newIssues, CacheAppender<DefaultIssue> cacheAppender) {
List<DefaultIssue> newIssuesList = newIssues
.peek(issueLifecycle::initNewOpenIssue)
.collect(MoreCollectors.toList());
}
}
- private void copyIssues(Component component, Map<DefaultIssue, DefaultIssue> matched, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
+ private void copyIssues(Component component, Map<DefaultIssue, DefaultIssue> matched, CacheAppender<DefaultIssue> cacheAppender) {
for (Map.Entry<DefaultIssue, DefaultIssue> entry : matched.entrySet()) {
DefaultIssue raw = entry.getKey();
DefaultIssue base = entry.getValue();
}
}
- private void fillExistingOpenIssues(Component component, Map<DefaultIssue, DefaultIssue> matched, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
+ private void fillExistingOpenIssues(Component component, Map<DefaultIssue, DefaultIssue> matched, CacheAppender<DefaultIssue> cacheAppender) {
for (Map.Entry<DefaultIssue, DefaultIssue> entry : matched.entrySet()) {
DefaultIssue raw = entry.getKey();
DefaultIssue base = entry.getValue();
}
}
- private void closeIssues(Component component, Stream<DefaultIssue> issues, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
+ private void closeIssues(Component component, Stream<DefaultIssue> issues, CacheAppender<DefaultIssue> cacheAppender) {
issues.forEach(issue -> {
// TODO should replace flag "beingClosed" by express call to transition "automaticClose"
issue.setBeingClosed(true);
});
}
- private void process(Component component, DefaultIssue issue, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
+ private void process(Component component, DefaultIssue issue, CacheAppender<DefaultIssue> cacheAppender) {
issueLifecycle.doAutomaticTransition(issue);
issueVisitors.onIssue(component, issue);
- cacheAppender.append(issue);
+ if (issue.isNew() || issue.isChanged() || issue.isCopied()) {
+ cacheAppender.append(issue);
+ }
}
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2020 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.ce.task.projectanalysis.issue;
-
-import java.io.File;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.TempFolder;
-import org.sonar.ce.task.projectanalysis.util.cache.DiskCache;
-import org.sonar.core.issue.DefaultIssue;
-
-/**
- * Cache of all the issues involved in the analysis. Their state is as it will be
- * persisted in database (after issue tracking, auto-assignment, ...)
- */
-public class IssueCache extends DiskCache<DefaultIssue> {
-
- // this constructor is used by picocontainer
- public IssueCache(TempFolder tempFolder, System2 system2) {
- super(tempFolder.newFile("issues", ".dat"), system2);
- }
-
- public IssueCache(File file, System2 system2) {
- super(file, system2);
- }
-}
result.setUserUuid(c.userUuid());
result.setCreationDate(c.creationDate());
// Don't copy "file" changelogs as they refer to file uuids that might later be purged
- c.diffs().entrySet().stream().filter(e -> !e.getKey().equals(IssueFieldsSetter.FILE))
+ c.diffs().entrySet().stream()
+ .filter(e -> !e.getKey().equals(IssueFieldsSetter.FILE))
.forEach(e -> result.setDiff(e.getKey(), e.getValue().oldValue(), e.getValue().newValue()));
if (result.diffs().isEmpty()) {
return Optional.empty();
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.ce.task.projectanalysis.issue;
+
+import java.io.File;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.TempFolder;
+import org.sonar.ce.task.projectanalysis.util.cache.ProtobufIssueDiskCache;
+
+/**
+ * Cache of all the issues involved in the analysis. Their state is as it will be
+ * persisted in database (after issue tracking, auto-assignment, ...)
+ */
+public class ProtoIssueCache extends ProtobufIssueDiskCache {
+
+ // this constructor is used by picocontainer
+ public ProtoIssueCache(TempFolder tempFolder, System2 system2) {
+ super(tempFolder.newFile("issues", ".dat"), system2);
+ }
+
+ public ProtoIssueCache(File file, System2 system2) {
+ super(file, system2);
+ }
+}
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import org.apache.commons.io.FileUtils;
import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.issue.IssueCache;
+import org.sonar.ce.task.projectanalysis.issue.ProtoIssueCache;
import org.sonar.ce.task.projectanalysis.issue.RuleRepository;
import org.sonar.ce.task.projectanalysis.issue.UpdateConflictResolver;
import org.sonar.ce.task.step.ComputationStep;
private final System2 system2;
private final UpdateConflictResolver conflictResolver;
private final RuleRepository ruleRepository;
- private final IssueCache issueCache;
+ private final ProtoIssueCache protoIssueCache;
private final IssueStorage issueStorage;
public PersistIssuesStep(DbClient dbClient, System2 system2, UpdateConflictResolver conflictResolver,
- RuleRepository ruleRepository, IssueCache issueCache, IssueStorage issueStorage) {
+ RuleRepository ruleRepository, ProtoIssueCache protoIssueCache, IssueStorage issueStorage) {
this.dbClient = dbClient;
this.system2 = system2;
this.conflictResolver = conflictResolver;
this.ruleRepository = ruleRepository;
- this.issueCache = issueCache;
+ this.protoIssueCache = protoIssueCache;
this.issueStorage = issueStorage;
}
@Override
public void execute(ComputationStep.Context context) {
+ context.getStatistics().add("cacheSize", FileUtils.byteCountToDisplaySize(protoIssueCache.fileSize()));
IssueStatistics statistics = new IssueStatistics();
try (DbSession dbSession = dbClient.openSession(true);
- CloseableIterator<DefaultIssue> issues = issueCache.traverse()) {
+
+ CloseableIterator<DefaultIssue> issues = protoIssueCache.traverse()) {
List<DefaultIssue> addedIssues = new ArrayList<>(ISSUE_BATCHING_SIZE);
List<DefaultIssue> updatedIssues = new ArrayList<>(ISSUE_BATCHING_SIZE);
persistUpdatedIssues(statistics, updatedIssues, mapper, changeMapper);
updatedIssues.clear();
}
- } else {
- statistics.untouched++;
}
}
persistNewIssues(statistics, addedIssues, mapper, changeMapper);
private int inserts = 0;
private int updates = 0;
private int merged = 0;
- private int untouched = 0;
private void dumpTo(ComputationStep.Context context) {
context.getStatistics()
.add("inserts", String.valueOf(inserts))
.add("updates", String.valueOf(updates))
- .add("merged", String.valueOf(merged))
- .add("untouched", String.valueOf(untouched));
+ .add("merged", String.valueOf(merged));
}
}
}
import org.sonar.ce.task.projectanalysis.analysis.Branch;
import org.sonar.ce.task.projectanalysis.component.Component;
import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
-import org.sonar.ce.task.projectanalysis.issue.IssueCache;
+import org.sonar.ce.task.projectanalysis.issue.ProtoIssueCache;
import org.sonar.ce.task.projectanalysis.notification.NotificationFactory;
import org.sonar.ce.task.step.ComputationStep;
import org.sonar.core.issue.DefaultIssue;
*/
static final Set<Class<? extends Notification>> NOTIF_TYPES = ImmutableSet.of(NewIssuesNotification.class, MyNewIssuesNotification.class, IssuesChangesNotification.class);
- private final IssueCache issueCache;
+ private final ProtoIssueCache protoIssueCache;
private final TreeRootHolder treeRootHolder;
private final NotificationService service;
private final AnalysisMetadataHolder analysisMetadataHolder;
private final NotificationFactory notificationFactory;
private final DbClient dbClient;
- public SendIssueNotificationsStep(IssueCache issueCache, TreeRootHolder treeRootHolder,
+ public SendIssueNotificationsStep(ProtoIssueCache protoIssueCache, TreeRootHolder treeRootHolder,
NotificationService service, AnalysisMetadataHolder analysisMetadataHolder,
NotificationFactory notificationFactory, DbClient dbClient) {
- this.issueCache = issueCache;
+ this.protoIssueCache = protoIssueCache;
this.treeRootHolder = treeRootHolder;
this.service = service;
this.analysisMetadataHolder = analysisMetadataHolder;
NewIssuesStatistics newIssuesStats = new NewIssuesStatistics(onCurrentAnalysis);
Map<String, UserDto> assigneesByUuid;
try (DbSession dbSession = dbClient.openSession(false)) {
- Iterable<DefaultIssue> iterable = issueCache::traverse;
+ Iterable<DefaultIssue> iterable = protoIssueCache::traverse;
Set<String> assigneeUuids = stream(iterable.spliterator(), false).map(DefaultIssue::assignee).filter(Objects::nonNull).collect(Collectors.toSet());
assigneesByUuid = dbClient.userDao().selectByUuids(dbSession, assigneeUuids).stream().collect(toMap(UserDto::getUuid, dto -> dto));
}
- try (CloseableIterator<DefaultIssue> issues = issueCache.traverse()) {
+ try (CloseableIterator<DefaultIssue> issues = protoIssueCache.traverse()) {
processIssues(newIssuesStats, issues, assigneesByUuid, notificationStatistics);
}
if (newIssuesStats.hasIssuesOnCurrentAnalysis()) {
*/
package org.sonar.ce.task.projectanalysis.util.cache;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.ObjectOutputStream;
-import java.io.OutputStream;
import java.io.Serializable;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.IOUtils;
-import org.sonar.api.utils.System2;
import org.sonar.core.util.CloseableIterator;
-/**
- * Serialize and deserialize objects on disk. No search capabilities, only traversal (full scan).
- */
-public class DiskCache<O extends Serializable> {
-
- private final File file;
- private final System2 system2;
-
- public DiskCache(File file, System2 system2) {
- this.system2 = system2;
- this.file = file;
- OutputStream output = null;
- boolean threw = true;
- try {
- // writes the serialization stream header required when calling "traverse()"
- // on empty stream. Moreover it allows to call multiple times "newAppender()"
- output = new ObjectOutputStream(new FileOutputStream(file));
- output.flush();
- threw = false;
- } catch (IOException e) {
- throw new IllegalStateException("Fail to write into file: " + file, e);
- } finally {
- if (threw) {
- // do not hide initial exception
- IOUtils.closeQuietly(output);
- } else {
- // raise an exception if can't close
- system2.close(output);
- }
- }
- }
-
- public DiskAppender newAppender() {
- return new DiskAppender();
- }
-
- public CloseableIterator<O> traverse() {
- try {
- return new ObjectInputStreamIterator<>(FileUtils.openInputStream(file));
- } catch (IOException e) {
- throw new IllegalStateException("Fail to traverse file: " + file, e);
- }
- }
+public interface DiskCache<O extends Serializable> {
+ long fileSize();
- public class DiskAppender implements AutoCloseable {
- private final ObjectOutputStream output;
+ CacheAppender<O> newAppender();
- private DiskAppender() {
- try {
- this.output = new ObjectOutputStream(new FileOutputStream(file, true)) {
- @Override
- protected void writeStreamHeader() {
- // do not write stream headers as it's already done in constructor of DiskCache
- }
- };
- } catch (IOException e) {
- throw new IllegalStateException("Fail to open file " + file, e);
- }
- }
+ CloseableIterator<O> traverse();
- public DiskAppender append(O object) {
- try {
- output.writeObject(object);
- output.reset();
- return this;
- } catch (IOException e) {
- throw new IllegalStateException("Fail to write into file " + file, e);
- }
- }
+ interface CacheAppender<I extends Serializable> extends AutoCloseable {
+ CacheAppender<I> append(I object);
@Override
- public void close() {
- system2.close(output);
- }
+ void close();
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.ce.task.projectanalysis.util.cache;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.CloseableIterator;
+
+/**
+ * Serialize and deserialize objects on disk. No search capabilities, only traversal (full scan).
+ */
+public class JavaSerializationDiskCache<O extends Serializable> implements DiskCache<O> {
+
+ private final File file;
+ private final System2 system2;
+
+ public JavaSerializationDiskCache(File file, System2 system2) {
+ this.system2 = system2;
+ this.file = file;
+ OutputStream output = null;
+ boolean threw = true;
+ try {
+ // writes the serialization stream header required when calling "traverse()"
+ // on empty stream. Moreover it allows to call multiple times "newAppender()"
+ output = new ObjectOutputStream(new FileOutputStream(file));
+ output.flush();
+ threw = false;
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to write into file: " + file, e);
+ } finally {
+ if (threw) {
+ // do not hide initial exception
+ IOUtils.closeQuietly(output);
+ } else {
+ // raise an exception if can't close
+ system2.close(output);
+ }
+ }
+ }
+
+ @Override
+ public long fileSize() {
+ return file.length();
+ }
+
+ @Override
+ public CacheAppender<O> newAppender() {
+ return new JavaSerializationCacheAppender();
+ }
+
+ @Override
+ public CloseableIterator<O> traverse() {
+ try {
+ return new ObjectInputStreamIterator<>(FileUtils.openInputStream(file));
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to traverse file: " + file, e);
+ }
+ }
+
+ public class JavaSerializationCacheAppender<O extends Serializable> implements CacheAppender<O> {
+ private final ObjectOutputStream output;
+
+ private JavaSerializationCacheAppender() {
+ try {
+ this.output = new ObjectOutputStream(new FileOutputStream(file, true)) {
+ @Override
+ protected void writeStreamHeader() {
+ // do not write stream headers as it's already done in constructor of DiskCache
+ }
+ };
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to open file " + file, e);
+ }
+ }
+
+ @Override
+ public CacheAppender append(O object) {
+ try {
+ output.writeObject(object);
+ output.reset();
+ return this;
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to write into file " + file, e);
+ }
+ }
+
+ @Override
+ public void close() {
+ system2.close(output);
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.ce.task.projectanalysis.util.cache;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableSet;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.utils.Duration;
+import org.sonar.api.utils.System2;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.DefaultIssueComment;
+import org.sonar.core.issue.FieldDiffs;
+import org.sonar.core.util.CloseableIterator;
+import org.sonar.core.util.Protobuf;
+import org.sonar.db.protobuf.DbIssues;
+
+import static java.util.Optional.ofNullable;
+
+public class ProtobufIssueDiskCache implements DiskCache<DefaultIssue> {
+ private static final String TAGS_SEPARATOR = ",";
+ private static final Splitter TAGS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
+
+ private final File file;
+ private final System2 system2;
+
+ public ProtobufIssueDiskCache(File file, System2 system2) {
+ this.file = file;
+ this.system2 = system2;
+ }
+
+ @Override
+ public long fileSize() {
+ return file.length();
+ }
+
+ @Override
+ public CacheAppender<DefaultIssue> newAppender() {
+ try {
+ return new ProtoCacheAppender();
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public CloseableIterator<DefaultIssue> traverse() {
+ CloseableIterator<IssueCache.Issue> protoIterator = Protobuf.readStream(file, IssueCache.Issue.parser());
+ return new CloseableIterator<DefaultIssue>() {
+ @CheckForNull
+ @Override
+ protected DefaultIssue doNext() {
+ if (protoIterator.hasNext()) {
+ return toDefaultIssue(protoIterator.next());
+ }
+ return null;
+ }
+
+ @Override
+ protected void doClose() {
+ protoIterator.close();
+ }
+ };
+ }
+
+ private static DefaultIssue toDefaultIssue(IssueCache.Issue next) {
+ DefaultIssue defaultIssue = new DefaultIssue();
+ defaultIssue.setKey(next.getKey());
+ defaultIssue.setType(RuleType.valueOf(next.getRuleType()));
+ defaultIssue.setComponentUuid(next.hasComponentUuid() ? next.getComponentUuid() : null);
+ defaultIssue.setComponentKey(next.getComponentKey());
+ defaultIssue.setModuleUuid(next.hasModuleUuid() ? next.getModuleUuid() : null);
+ defaultIssue.setModuleUuidPath(next.hasModuleUuidPath() ? next.getModuleUuidPath() : null);
+ defaultIssue.setProjectUuid(next.getProjectUuid());
+ defaultIssue.setProjectKey(next.getProjectKey());
+ defaultIssue.setRuleKey(RuleKey.parse(next.getRuleKey()));
+ defaultIssue.setLanguage(next.hasLanguage() ? next.getLanguage() : null);
+ defaultIssue.setSeverity(next.hasSeverity() ? next.getSeverity() : null);
+ defaultIssue.setManualSeverity(next.getManualSeverity());
+ defaultIssue.setMessage(next.hasMessage() ? next.getMessage() : null);
+ defaultIssue.setLine(next.hasLine() ? next.getLine() : null);
+ defaultIssue.setGap(next.hasGap() ? next.getGap() : null);
+ defaultIssue.setEffort(next.hasEffort() ? Duration.create(next.getEffort()) : null);
+ defaultIssue.setStatus(next.getStatus());
+ defaultIssue.setResolution(next.hasResolution() ? next.getResolution() : null);
+ defaultIssue.setAssigneeUuid(next.hasAssigneeUuid() ? next.getAssigneeUuid() : null);
+ defaultIssue.setChecksum(next.hasChecksum() ? next.getChecksum() : null);
+ defaultIssue.setAttributes(next.getAttributesMap());
+ defaultIssue.setAuthorLogin(next.hasAuthorLogin() ? next.getAuthorLogin() : null);
+ next.getCommentsList().forEach(c -> defaultIssue.addComment(toDefaultIssueComment(c)));
+ defaultIssue.setTags(ImmutableSet.copyOf(TAGS_SPLITTER.split(next.hasTags() ? "" : next.getTags())));
+ defaultIssue.setLocations(next.hasLocations() ? next.getLocations() : null);
+ defaultIssue.setIsFromExternalRuleEngine(next.getIsFromExternalRuleEngine());
+ defaultIssue.setCreationDate(new Date(next.getCreationDate()));
+ defaultIssue.setUpdateDate(next.hasUpdateDate() ? new Date(next.getUpdateDate()) : null);
+ defaultIssue.setCloseDate(next.hasCloseDate() ? new Date(next.getCloseDate()) : null);
+ defaultIssue.setCurrentChangeWithoutAddChange(next.hasCurrentChanges() ? toDefaultIssueChanges(next.getCurrentChanges()) : null);
+ defaultIssue.setNew(next.getIsNew());
+ defaultIssue.setCopied(next.getIsCopied());
+ defaultIssue.setBeingClosed(next.getBeingClosed());
+ defaultIssue.setOnDisabledRule(next.getOnDisabledRule());
+ defaultIssue.setChanged(next.getIsChanged());
+ defaultIssue.setSendNotifications(next.getSendNotifications());
+ defaultIssue.setSelectedAt(next.hasSelectedAt() ? next.getSelectedAt() : null);
+
+ for (IssueCache.FieldDiffs protoFieldDiffs : next.getChangesList()) {
+ defaultIssue.addChange(toDefaultIssueChanges(protoFieldDiffs));
+ }
+
+ return defaultIssue;
+ }
+
+ private static IssueCache.Issue toProto(IssueCache.Issue.Builder builder, DefaultIssue defaultIssue) {
+ builder.clear();
+ builder.setKey(defaultIssue.key());
+ builder.setRuleType(defaultIssue.type().getDbConstant());
+ ofNullable(defaultIssue.componentUuid()).ifPresent(builder::setComponentUuid);
+ builder.setComponentKey(defaultIssue.componentKey());
+ ofNullable(defaultIssue.moduleUuid()).ifPresent(builder::setModuleUuid);
+ ofNullable(defaultIssue.moduleUuidPath()).ifPresent(builder::setModuleUuidPath);
+ builder.setProjectUuid(defaultIssue.projectUuid());
+ builder.setProjectKey(defaultIssue.projectKey());
+ builder.setRuleKey(defaultIssue.ruleKey().toString());
+ ofNullable(defaultIssue.language()).ifPresent(builder::setLanguage);
+ ofNullable(defaultIssue.severity()).ifPresent(builder::setSeverity);
+ builder.setManualSeverity(defaultIssue.manualSeverity());
+ ofNullable(defaultIssue.message()).ifPresent(builder::setMessage);
+ ofNullable(defaultIssue.line()).ifPresent(builder::setLine);
+ ofNullable(defaultIssue.gap()).ifPresent(builder::setGap);
+ ofNullable(defaultIssue.effort()).map(Duration::toMinutes).ifPresent(builder::setEffort);
+ builder.setStatus(defaultIssue.status());
+ ofNullable(defaultIssue.resolution()).ifPresent(builder::setResolution);
+ ofNullable(defaultIssue.assignee()).ifPresent(builder::setAssigneeUuid);
+ ofNullable(defaultIssue.checksum()).ifPresent(builder::setChecksum);
+ ofNullable(defaultIssue.attributes()).ifPresent(builder::putAllAttributes);
+ ofNullable(defaultIssue.authorLogin()).ifPresent(builder::setAuthorLogin);
+ defaultIssue.defaultIssueComments().forEach(c -> builder.addComments(toProtoComment(c)));
+ ofNullable(defaultIssue.tags()).ifPresent(t -> builder.setTags(String.join(TAGS_SEPARATOR, t)));
+ ofNullable(defaultIssue.getLocations()).ifPresent(l -> builder.setLocations((DbIssues.Locations) l));
+ builder.setIsFromExternalRuleEngine(defaultIssue.isFromExternalRuleEngine());
+ builder.setCreationDate(defaultIssue.creationDate().getTime());
+ ofNullable(defaultIssue.updateDate()).map(Date::getTime).ifPresent(builder::setUpdateDate);
+ ofNullable(defaultIssue.closeDate()).map(Date::getTime).ifPresent(builder::setCloseDate);
+ ofNullable(defaultIssue.currentChange()).ifPresent(c -> builder.setCurrentChanges(toProtoIssueChanges(c)));
+ builder.setIsNew(defaultIssue.isNew());
+ builder.setIsCopied(defaultIssue.isCopied());
+ builder.setBeingClosed(defaultIssue.isBeingClosed());
+ builder.setOnDisabledRule(defaultIssue.isOnDisabledRule());
+ builder.setIsChanged(defaultIssue.isChanged());
+ builder.setSendNotifications(defaultIssue.mustSendNotifications());
+ ofNullable(defaultIssue.selectedAt()).ifPresent(builder::setSelectedAt);
+
+ for (FieldDiffs fieldDiffs : defaultIssue.changes()) {
+ builder.addChanges(toProtoIssueChanges(fieldDiffs));
+ }
+
+ return builder.build();
+ }
+
+ private static DefaultIssueComment toDefaultIssueComment(IssueCache.Comment comment) {
+ DefaultIssueComment issueComment = new DefaultIssueComment()
+ .setCreatedAt(new Date(comment.getCreatedAt()))
+ .setUpdatedAt(new Date(comment.getUpdatedAt()))
+ .setNew(comment.getIsNew())
+ .setKey(comment.getKey())
+ .setIssueKey(comment.getIssueKey())
+ .setMarkdownText(comment.getMarkdownText());
+
+ if (comment.hasUserUuid()) {
+ issueComment.setUserUuid(comment.getUserUuid());
+ }
+ return issueComment;
+ }
+
+ private static IssueCache.Comment toProtoComment(DefaultIssueComment comment) {
+ IssueCache.Comment.Builder builder = IssueCache.Comment.newBuilder()
+ .setCreatedAt(comment.createdAt().getTime())
+ .setUpdatedAt(comment.updatedAt().getTime())
+ .setIsNew(comment.isNew())
+ .setKey(comment.key())
+ .setIssueKey(comment.issueKey())
+ .setMarkdownText(comment.markdownText());
+
+ if (comment.userUuid() != null) {
+ builder.setUserUuid(comment.userUuid());
+ }
+ return builder.build();
+ }
+
+ private static FieldDiffs toDefaultIssueChanges(IssueCache.FieldDiffs fieldDiffs) {
+ FieldDiffs defaultIssueFieldDiffs = new FieldDiffs()
+ .setUserUuid(fieldDiffs.getUserUuid())
+ .setCreationDate(new Date(fieldDiffs.getCreationDate()));
+
+ if (fieldDiffs.hasIssueKey()) {
+ defaultIssueFieldDiffs.setIssueKey(fieldDiffs.getIssueKey());
+ }
+
+ for (Map.Entry<String, IssueCache.Diff> e : fieldDiffs.getDiffsMap().entrySet()) {
+ defaultIssueFieldDiffs.setDiff(e.getKey(),
+ e.getValue().hasOldValue() ? e.getValue().getOldValue() : null,
+ e.getValue().hasNewValue() ? e.getValue().getNewValue() : null);
+ }
+
+ return defaultIssueFieldDiffs;
+ }
+
+ private static IssueCache.FieldDiffs toProtoIssueChanges(FieldDiffs fieldDiffs) {
+ IssueCache.FieldDiffs.Builder builder = IssueCache.FieldDiffs.newBuilder()
+ .setCreationDate(fieldDiffs.creationDate().getTime());
+
+ if (fieldDiffs.issueKey() != null) {
+ builder.setIssueKey(fieldDiffs.issueKey());
+ }
+
+ String userUuid = fieldDiffs.userUuid();
+ if (userUuid != null) {
+ builder.setUserUuid(userUuid);
+ }
+
+ for (Map.Entry<String, FieldDiffs.Diff> e : fieldDiffs.diffs().entrySet()) {
+ IssueCache.Diff.Builder diffBuilder = IssueCache.Diff.newBuilder();
+ if (e.getValue().oldValue() != null) {
+ diffBuilder.setOldValue(e.getValue().oldValue().toString());
+ }
+ if (e.getValue().newValue() != null) {
+ diffBuilder.setNewValue(e.getValue().newValue().toString());
+
+ }
+ builder.putDiffs(e.getKey(), diffBuilder.build());
+ }
+
+ return builder.build();
+ }
+
+ private class ProtoCacheAppender implements CacheAppender<DefaultIssue> {
+ private final OutputStream out;
+ private final IssueCache.Issue.Builder builder;
+
+ private ProtoCacheAppender() throws FileNotFoundException {
+ this.out = new BufferedOutputStream(new FileOutputStream(file, true));
+ this.builder = IssueCache.Issue.newBuilder();
+ }
+
+ @Override
+ public CacheAppender append(DefaultIssue object) {
+ Protobuf.writeStream(Collections.singleton(toProto(builder, object)), out);
+ return this;
+ }
+
+ @Override
+ public void close() {
+ system2.close(out);
+ }
+ }
+}
--- /dev/null
+// SonarQube, open source software quality management tool.
+// Copyright (C) 2008-2016 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.
+
+
+// IMPORTANT
+// This is beta version of specification. It will evolve during next
+// releases and is not forward-compatible yet.
+
+syntax = "proto2";
+
+import "db-issues.proto";
+
+option java_package = "org.sonar.ce.task.projectanalysis.util.cache";
+option optimize_for = SPEED;
+
+message Issue {
+ optional string key = 1;
+ optional int32 ruleType = 2;
+ optional string componentUuid = 3;
+ optional string componentKey = 4;
+ optional string moduleUuid = 5;
+ optional string moduleUuidPath = 6;
+ optional string projectUuid = 7;
+ optional string projectKey = 8;
+ optional string ruleKey = 9;
+ optional string language = 10;
+ optional string severity = 11;
+ optional bool manualSeverity = 12;
+ optional string message = 13;
+ optional int32 line = 14;
+ optional double gap = 15;
+ optional int64 effort = 16;
+ optional string status = 17;
+ optional string resolution = 18;
+ optional string assigneeUuid = 19;
+ optional string checksum = 20;
+ map<string, string> attributes = 21;
+ optional string authorLogin = 22;
+ optional string tags = 23;
+ optional sonarqube.db.issues.Locations locations = 24;
+
+ optional bool isFromExternalRuleEngine = 25;
+
+ // FUNCTIONAL DATES
+ optional int64 creationDate = 26;
+ optional int64 updateDate = 27;
+ optional int64 closeDate = 28;
+
+ repeated FieldDiffs changes = 29;
+ optional FieldDiffs currentChanges = 30;
+
+ optional bool isNew = 31;
+ optional bool isCopied = 32;
+ optional bool beingClosed = 33;
+ optional bool onDisabledRule = 34;
+ optional bool isChanged = 35;
+ optional bool sendNotifications = 36;
+ optional int64 selectedAt = 37;
+
+ repeated Comment comments = 38;
+}
+
+message Comment {
+ optional string issueKey = 1;
+ optional string userUuid = 2;
+ optional int64 createdAt = 3;
+ optional int64 updatedAt = 4;
+ optional string key = 5;
+ optional string markdownText = 6;
+ optional bool isNew = 7;
+}
+
+message FieldDiffs {
+ optional string userUuid = 1;
+ optional int64 creationDate = 2;
+ optional string issueKey = 3;
+ map<string, Diff> diffs = 4;
+}
+
+message Diff {
+ optional string oldValue = 1;
+ optional string newValue = 2;
+}
import java.util.Arrays;
import java.util.Collections;
+import java.util.Date;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.System2;
import org.sonar.ce.task.projectanalysis.component.ReportComponent;
import org.sonar.ce.task.projectanalysis.component.VisitorsCrawler;
ComponentIssuesLoader issuesLoader = mock(ComponentIssuesLoader.class);
ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues = mock(ComponentsWithUnprocessedIssues.class);
IssueLifecycle issueLifecycle = mock(IssueLifecycle.class);
- IssueCache issueCache;
+ ProtoIssueCache protoIssueCache;
VisitorsCrawler underTest;
@Before
public void setUp() throws Exception {
- issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
+ protoIssueCache = new ProtoIssueCache(temp.newFile(), System2.INSTANCE);
underTest = new VisitorsCrawler(
- Arrays.asList(new CloseIssuesOnRemovedComponentsVisitor(issuesLoader, componentsWithUnprocessedIssues, issueCache, issueLifecycle)));
+ Arrays.asList(new CloseIssuesOnRemovedComponentsVisitor(issuesLoader, componentsWithUnprocessedIssues, protoIssueCache, issueLifecycle)));
}
@Test
String issueUuid = "ABCD";
when(componentsWithUnprocessedIssues.getUuids()).thenReturn(newHashSet(fileUuid));
- DefaultIssue issue = new DefaultIssue().setKey(issueUuid);
+ DefaultIssue issue = new DefaultIssue().setKey(issueUuid).setType(RuleType.BUG).setCreationDate(new Date())
+ .setComponentKey("c").setProjectUuid("u").setProjectKey("k").setRuleKey(RuleKey.of("r", "r")).setStatus("OPEN");
when(issuesLoader.loadOpenIssues(fileUuid)).thenReturn(Collections.singletonList(issue));
underTest.visit(ReportComponent.builder(PROJECT, 1).build());
verify(issueLifecycle).doAutomaticTransition(issue);
- CloseableIterator<DefaultIssue> issues = issueCache.traverse();
+ CloseableIterator<DefaultIssue> issues = protoIssueCache.traverse();
assertThat(issues.hasNext()).isTrue();
DefaultIssue result = issues.next();
underTest.visit(ReportComponent.builder(PROJECT, 1).build());
verifyZeroInteractions(issueLifecycle);
- CloseableIterator<DefaultIssue> issues = issueCache.traverse();
+ CloseableIterator<DefaultIssue> issues = protoIssueCache.traverse();
assertThat(issues.hasNext()).isFalse();
}
underTest.visit(ReportComponent.builder(DIRECTORY, 1).build());
verifyZeroInteractions(issueLifecycle);
- CloseableIterator<DefaultIssue> issues = issueCache.traverse();
+ CloseableIterator<DefaultIssue> issues = protoIssueCache.traverse();
assertThat(issues.hasNext()).isFalse();
}
underTest.visit(ReportComponent.builder(FILE, 1).build());
verifyZeroInteractions(issueLifecycle);
- CloseableIterator<DefaultIssue> issues = issueCache.traverse();
+ CloseableIterator<DefaultIssue> issues = protoIssueCache.traverse();
assertThat(issues.hasNext()).isFalse();
}
}
*/
package org.sonar.ce.task.projectanalysis.issue;
+import java.util.Date;
import java.util.List;
import java.util.Optional;
import org.junit.Before;
import org.sonar.ce.task.projectanalysis.source.SourceLinesHashRepository;
import org.sonar.ce.task.projectanalysis.source.SourceLinesRepositoryRule;
import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.FieldDiffs;
+import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.issue.tracking.Tracker;
import org.sonar.db.DbClient;
import org.sonar.db.DbTester;
import org.sonar.scanner.protocol.Constants;
import org.sonar.scanner.protocol.output.ScannerReport;
import org.sonar.server.issue.IssueFieldsSetter;
+import org.sonar.server.issue.workflow.IssueWorkflow;
import static com.google.common.collect.Lists.newArrayList;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
private AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class);
private IssueFilter issueFilter = mock(IssueFilter.class);
private MovedFilesRepository movedFilesRepository = mock(MovedFilesRepository.class);
- private IssueLifecycle issueLifecycle = mock(IssueLifecycle.class);
+ private IssueChangeContext issueChangeContext = mock(IssueChangeContext.class);
+ private IssueLifecycle issueLifecycle = new IssueLifecycle(analysisMetadataHolder, issueChangeContext, mock(IssueWorkflow.class), new IssueFieldsSetter(),
+ mock(DebtCalculator.class), ruleRepositoryRule);
private IssueVisitor issueVisitor = mock(IssueVisitor.class);
private ReferenceBranchComponentUuids mergeBranchComponentsUuids = mock(ReferenceBranchComponentUuids.class);
private SiblingsIssueMerger issueStatusCopier = mock(SiblingsIssueMerger.class);
private PullRequestTrackerExecution prBranchTracker;
private ReferenceBranchTrackerExecution mergeBranchTracker;
private ActiveRulesHolder activeRulesHolder = new AlwaysActiveRulesHolderImpl();
- private IssueCache issueCache;
+ private ProtoIssueCache protoIssueCache;
private TypeAwareVisitor underTest;
mergeBranchTracker = new ReferenceBranchTrackerExecution(rawInputFactory, mergeInputFactory, new Tracker<>());
trackingDelegator = new IssueTrackingDelegator(prBranchTracker, mergeBranchTracker, tracker, analysisMetadataHolder);
treeRootHolder.setRoot(PROJECT);
- issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
+ protoIssueCache = new ProtoIssueCache(temp.newFile(), System2.INSTANCE);
when(issueFilter.accept(any(DefaultIssue.class), eq(FILE))).thenReturn(true);
- underTest = new IntegrateIssuesVisitor(issueCache, issueLifecycle, issueVisitors, trackingDelegator, issueStatusCopier, referenceBranchComponentUuids);
+ when(issueChangeContext.date()).thenReturn(new Date());
+ underTest = new IntegrateIssuesVisitor(protoIssueCache, issueLifecycle, issueVisitors, trackingDelegator, issueStatusCopier, referenceBranchComponentUuids);
}
@Test
public void process_new_issue() {
+ ruleRepositoryRule.add(RuleKey.of("xoo", "S001"));
when(analysisMetadataHolder.isBranch()).thenReturn(true);
ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
.setMsg("the message")
underTest.visitAny(FILE);
- verify(issueLifecycle).initNewOpenIssue(defaultIssueCaptor.capture());
- DefaultIssue capturedIssue = defaultIssueCaptor.getValue();
- assertThat(capturedIssue.ruleKey().rule()).isEqualTo("S001");
+ assertThat(newArrayList(protoIssueCache.traverse())).hasSize(1);
+ }
- verify(issueStatusCopier).tryMerge(FILE, singletonList(capturedIssue));
+ @Test
+ public void process_existing_issue() {
- verify(issueLifecycle).doAutomaticTransition(capturedIssue);
+ RuleKey ruleKey = RuleTesting.XOO_X1;
+ // Issue from db has severity major
+ addBaseIssue(ruleKey);
+
+ // Issue from report has severity blocker
+ ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
+ .setMsg("new message")
+ .setRuleRepository(ruleKey.repository())
+ .setRuleKey(ruleKey.rule())
+ .setSeverity(Constants.Severity.BLOCKER)
+ .build();
+ reportReader.putIssues(FILE_REF, asList(reportIssue));
+ fileSourceRepository.addLine(FILE_REF, "line1");
+
+ underTest.visitAny(FILE);
+
+ List<DefaultIssue> issues = newArrayList(protoIssueCache.traverse());
+ assertThat(issues).hasSize(1);
+ assertThat(issues.get(0).severity()).isEqualTo(Severity.BLOCKER);
- assertThat(newArrayList(issueCache.traverse())).hasSize(1);
}
@Test
- public void process_existing_issue() {
+ public void dont_cache_existing_issue_if_unmodified() {
RuleKey ruleKey = RuleTesting.XOO_X1;
// Issue from db has severity major
underTest.visitAny(FILE);
- ArgumentCaptor<DefaultIssue> rawIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
- ArgumentCaptor<DefaultIssue> baseIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
- verify(issueLifecycle).mergeExistingOpenIssue(rawIssueCaptor.capture(), baseIssueCaptor.capture());
- assertThat(rawIssueCaptor.getValue().severity()).isEqualTo(Severity.BLOCKER);
- assertThat(baseIssueCaptor.getValue().severity()).isEqualTo(Severity.MAJOR);
-
- verify(issueLifecycle).doAutomaticTransition(defaultIssueCaptor.capture());
- assertThat(defaultIssueCaptor.getValue().ruleKey()).isEqualTo(ruleKey);
- List<DefaultIssue> issues = newArrayList(issueCache.traverse());
+ List<DefaultIssue> issues = newArrayList(protoIssueCache.traverse());
assertThat(issues).hasSize(1);
assertThat(issues.get(0).severity()).isEqualTo(Severity.BLOCKER);
@Test
public void execute_issue_visitors() {
+ ruleRepositoryRule.add(RuleKey.of("xoo", "S001"));
ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
.setMsg("the message")
.setRuleRepository("xoo")
addBaseIssue(ruleKey);
// No issue in the report
-
underTest.visitAny(FILE);
- verify(issueLifecycle).doAutomaticTransition(defaultIssueCaptor.capture());
- assertThat(defaultIssueCaptor.getValue().isBeingClosed()).isTrue();
- List<DefaultIssue> issues = newArrayList(issueCache.traverse());
- assertThat(issues).hasSize(1);
+ List<DefaultIssue> issues = newArrayList(protoIssueCache.traverse());
+ assertThat(issues).hasSize(0);
}
@Test
underTest.visitAny(FILE);
- ArgumentCaptor<DefaultIssue> rawIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
- ArgumentCaptor<DefaultIssue> baseIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
- verify(issueLifecycle).copyExistingOpenIssueFromBranch(rawIssueCaptor.capture(), baseIssueCaptor.capture(), eq("master"));
- assertThat(rawIssueCaptor.getValue().severity()).isEqualTo(Severity.BLOCKER);
- assertThat(baseIssueCaptor.getValue().severity()).isEqualTo(Severity.MAJOR);
-
- verify(issueLifecycle).doAutomaticTransition(defaultIssueCaptor.capture());
- assertThat(defaultIssueCaptor.getValue().ruleKey()).isEqualTo(ruleKey);
- List<DefaultIssue> issues = newArrayList(issueCache.traverse());
+ List<DefaultIssue> issues = newArrayList(protoIssueCache.traverse());
assertThat(issues).hasSize(1);
assertThat(issues.get(0).severity()).isEqualTo(Severity.BLOCKER);
+ assertThat(issues.get(0).isNew()).isFalse();
+ assertThat(issues.get(0).isCopied()).isTrue();
+ assertThat(issues.get(0).changes()).hasSize(1);
+ assertThat(issues.get(0).changes().get(0).diffs()).contains(entry(IssueFieldsSetter.FROM_BRANCH, new FieldDiffs.Diff("master",null)));
}
private void addBaseIssue(RuleKey ruleKey) {
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
import org.sonar.ce.task.projectanalysis.issue.AdHocRuleCreator;
-import org.sonar.ce.task.projectanalysis.issue.IssueCache;
+import org.sonar.ce.task.projectanalysis.issue.ProtoIssueCache;
import org.sonar.ce.task.projectanalysis.issue.RuleRepositoryImpl;
import org.sonar.ce.task.projectanalysis.issue.UpdateConflictResolver;
import org.sonar.ce.task.projectanalysis.util.cache.DiskCache;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.MapEntry.entry;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
private DbSession session = db.getSession();
private DbClient dbClient = db.getDbClient();
private UpdateConflictResolver conflictResolver = mock(UpdateConflictResolver.class);
- private IssueCache issueCache;
+ private ProtoIssueCache protoIssueCache;
private ComputationStep underTest;
private AdHocRuleCreator adHocRuleCreator = mock(AdHocRuleCreator.class);
@Before
public void setup() throws Exception {
- issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
+ protoIssueCache = new ProtoIssueCache(temp.newFile(), System2.INSTANCE);
reportReader.setMetadata(ScannerReport.Metadata.getDefaultInstance());
- underTest = new PersistIssuesStep(dbClient, system2, conflictResolver, new RuleRepositoryImpl(adHocRuleCreator, dbClient, analysisMetadataHolder), issueCache,
+ underTest = new PersistIssuesStep(dbClient, system2, conflictResolver, new RuleRepositoryImpl(adHocRuleCreator, dbClient, analysisMetadataHolder), protoIssueCache,
new IssueStorage());
}
ComponentDto file = db.components().insertComponent(newFileDto(project, null));
when(system2.now()).thenReturn(NOW);
- issueCache.newAppender().append(new DefaultIssue()
+ protoIssueCache.newAppender().append(new DefaultIssue()
.setKey("ISSUE")
.setType(RuleType.CODE_SMELL)
.setRuleKey(rule.getKey())
.setComponentUuid(file.uuid())
+ .setComponentKey(file.getKey())
.setProjectUuid(project.uuid())
+ .setProjectKey(project.getKey())
.setSeverity(BLOCKER)
.setStatus(STATUS_OPEN)
.setNew(false)
.setCopied(true)
.setType(RuleType.BUG)
+ .setCreationDate(new Date(NOW))
.setSelectedAt(NOW)
.addComment(new DefaultIssueComment()
.setKey("COMMENT")
.setUserUuid("john_uuid")
.setMarkdownText("Some text")
.setCreatedAt(new Date(NOW))
+ .setUpdatedAt(new Date(NOW))
.setNew(true))
.setCurrentChange(
new FieldDiffs()
List<IssueChangeDto> changes = dbClient.issueChangeDao().selectByIssueKeys(session, Arrays.asList("ISSUE"));
assertThat(changes).extracting(IssueChangeDto::getChangeType).containsExactly(IssueChangeDto.TYPE_COMMENT, IssueChangeDto.TYPE_FIELD_CHANGE);
- assertThat(context.getStatistics().getAll()).containsOnly(
- entry("inserts", "1"), entry("updates", "0"), entry("merged", "0"), entry("untouched", "0"));
+ assertThat(context.getStatistics().getAll()).contains(
+ entry("inserts", "1"), entry("updates", "0"), entry("merged", "0"));
}
@Test
ComponentDto file = db.components().insertComponent(newFileDto(project, null));
when(system2.now()).thenReturn(NOW);
- issueCache.newAppender().append(new DefaultIssue()
+ protoIssueCache.newAppender().append(new DefaultIssue()
.setKey("ISSUE")
.setType(RuleType.CODE_SMELL)
.setRuleKey(rule.getKey())
.setComponentUuid(file.uuid())
+ .setComponentKey(file.getKey())
.setProjectUuid(project.uuid())
+ .setProjectKey(project.getKey())
.setSeverity(BLOCKER)
.setStatus(STATUS_OPEN)
.setNew(true)
.setCopied(true)
.setType(RuleType.BUG)
+ .setCreationDate(new Date(NOW))
.setSelectedAt(NOW)
.addComment(new DefaultIssueComment()
.setKey("COMMENT")
.setIssueKey("ISSUE")
.setUserUuid("john_uuid")
.setMarkdownText("Some text")
+ .setUpdatedAt(new Date(NOW))
.setCreatedAt(new Date(NOW))
.setNew(true))
.setCurrentChange(new FieldDiffs()
List<IssueChangeDto> changes = dbClient.issueChangeDao().selectByIssueKeys(session, Arrays.asList("ISSUE"));
assertThat(changes).extracting(IssueChangeDto::getChangeType).containsExactly(IssueChangeDto.TYPE_COMMENT, IssueChangeDto.TYPE_FIELD_CHANGE);
- assertThat(context.getStatistics().getAll()).containsOnly(
- entry("inserts", "1"), entry("updates", "0"), entry("merged", "0"), entry("untouched", "0"));
+ assertThat(context.getStatistics().getAll()).contains(
+ entry("inserts", "1"), entry("updates", "0"), entry("merged", "0"));
}
@Test
// simulate the issue has been updated after the analysis ran
.setUpdatedAt(NOW + 1_000_000_000L));
issue = dbClient.issueDao().selectByKey(db.getSession(), issue.getKey()).get();
- DiskCache<DefaultIssue>.DiskAppender issueCacheAppender = issueCache.newAppender();
+ DiskCache.CacheAppender issueCacheAppender = protoIssueCache.newAppender();
when(system2.now()).thenReturn(NOW);
DefaultIssue defaultIssue = issue.toDefaultIssue()
ArgumentCaptor<IssueDto> issueDtoCaptor = ArgumentCaptor.forClass(IssueDto.class);
verify(conflictResolver).resolve(eq(defaultIssue), issueDtoCaptor.capture(), any(IssueMapper.class));
assertThat(issueDtoCaptor.getValue().getId()).isEqualTo(issue.getId());
- assertThat(context.getStatistics().getAll()).containsOnly(
- entry("inserts", "0"), entry("updates", "1"), entry("merged", "1"), entry("untouched", "0"));
+ assertThat(context.getStatistics().getAll()).contains(
+ entry("inserts", "0"), entry("updates", "1"), entry("merged", "1"));
}
ComponentDto file = db.components().insertComponent(newFileDto(project, null));
session.commit();
- issueCache.newAppender().append(new DefaultIssue()
+ protoIssueCache.newAppender().append(new DefaultIssue()
.setKey("ISSUE")
.setType(RuleType.CODE_SMELL)
.setRuleKey(rule.getKey())
.setComponentUuid(file.uuid())
+ .setComponentKey(file.getKey())
.setProjectUuid(project.uuid())
+ .setProjectKey(project.getKey())
.setSeverity(BLOCKER)
.setStatus(STATUS_OPEN)
+ .setCreationDate(new Date(NOW))
.setNew(true)
.setType(RuleType.BUG)).close();
assertThat(result.getSeverity()).isEqualTo(BLOCKER);
assertThat(result.getStatus()).isEqualTo(STATUS_OPEN);
assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant());
- assertThat(context.getStatistics().getAll()).containsOnly(
- entry("inserts", "1"), entry("updates", "0"), entry("merged", "0"), entry("untouched", "0"));
+ assertThat(context.getStatistics().getAll()).contains(
+ entry("inserts", "1"), entry("updates", "0"), entry("merged", "0"));
}
@Test
.setResolution(null)
.setCreatedAt(NOW - 1_000_000_000L)
.setUpdatedAt(NOW - 1_000_000_000L));
- DiskCache<DefaultIssue>.DiskAppender issueCacheAppender = issueCache.newAppender();
+ DiskCache.CacheAppender issueCacheAppender = protoIssueCache.newAppender();
issueCacheAppender.append(
issue.toDefaultIssue()
IssueDto issueReloaded = db.getDbClient().issueDao().selectByKey(db.getSession(), issue.getKey()).get();
assertThat(issueReloaded.getStatus()).isEqualTo(STATUS_CLOSED);
assertThat(issueReloaded.getResolution()).isEqualTo(RESOLUTION_FIXED);
- assertThat(context.getStatistics().getAll()).containsOnly(
- entry("inserts", "0"), entry("updates", "1"), entry("merged", "0"), entry("untouched", "0"));
+ assertThat(context.getStatistics().getAll()).contains(
+ entry("inserts", "0"), entry("updates", "1"), entry("merged", "0"));
}
@Test
.setResolution(null)
.setCreatedAt(NOW - 1_000_000_000L)
.setUpdatedAt(NOW - 1_000_000_000L));
- DiskCache<DefaultIssue>.DiskAppender issueCacheAppender = issueCache.newAppender();
+ DiskCache.CacheAppender issueCacheAppender = protoIssueCache.newAppender();
issueCacheAppender.append(
issue.toDefaultIssue()
.setUserUuid("john_uuid")
.setMarkdownText("Some text")
.setCreatedAt(new Date(NOW))
+ .setUpdatedAt(new Date(NOW))
.setNew(true)))
.close();
.extracting(IssueChangeDto::getChangeType, IssueChangeDto::getUserUuid, IssueChangeDto::getChangeData, IssueChangeDto::getIssueKey,
IssueChangeDto::getIssueChangeCreationDate)
.containsOnly(IssueChangeDto.TYPE_COMMENT, "john_uuid", "Some text", issue.getKey(), NOW);
- assertThat(context.getStatistics().getAll()).containsOnly(
- entry("inserts", "0"), entry("updates", "1"), entry("merged", "0"), entry("untouched", "0"));
+ assertThat(context.getStatistics().getAll()).contains(
+ entry("inserts", "0"), entry("updates", "1"), entry("merged", "0"));
}
@Test
.setResolution(null)
.setCreatedAt(NOW - 1_000_000_000L)
.setUpdatedAt(NOW - 1_000_000_000L));
- DiskCache<DefaultIssue>.DiskAppender issueCacheAppender = issueCache.newAppender();
+ DiskCache.CacheAppender issueCacheAppender = protoIssueCache.newAppender();
issueCacheAppender.append(
issue.toDefaultIssue()
.extracting(IssueChangeDto::getChangeType, IssueChangeDto::getUserUuid, IssueChangeDto::getChangeData, IssueChangeDto::getIssueKey,
IssueChangeDto::getIssueChangeCreationDate)
.containsOnly(IssueChangeDto.TYPE_FIELD_CHANGE, "john_uuid", "technicalDebt=1", issue.getKey(), NOW);
- assertThat(context.getStatistics().getAll()).containsOnly(
- entry("inserts", "0"), entry("updates", "1"), entry("merged", "0"), entry("untouched", "0"));
+ assertThat(context.getStatistics().getAll()).contains(
+ entry("inserts", "0"), entry("updates", "1"), entry("merged", "0"));
}
}
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.sonar.api.notifications.Notification;
+import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.Duration;
import org.sonar.api.utils.System2;
import org.sonar.ce.task.projectanalysis.component.Component;
import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl;
import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-import org.sonar.ce.task.projectanalysis.issue.IssueCache;
+import org.sonar.ce.task.projectanalysis.issue.ProtoIssueCache;
import org.sonar.ce.task.projectanalysis.notification.NotificationFactory;
import org.sonar.ce.task.projectanalysis.util.cache.DiskCache;
import org.sonar.ce.task.step.ComputationStep;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
private NewIssuesNotification newIssuesNotificationMock = createNewIssuesNotificationMock();
private MyNewIssuesNotification myNewIssuesNotificationMock = createMyNewIssuesNotificationMock();
- private IssueCache issueCache;
+ private ProtoIssueCache protoIssueCache;
private SendIssueNotificationsStep underTest;
@Before
public void setUp() throws Exception {
- issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
- underTest = new SendIssueNotificationsStep(issueCache, treeRootHolder, notificationService, analysisMetadataHolder,
+ protoIssueCache = new ProtoIssueCache(temp.newFile(), System2.INSTANCE);
+ underTest = new SendIssueNotificationsStep(protoIssueCache, treeRootHolder, notificationService, analysisMetadataHolder,
notificationFactory, db.getDbClient());
when(notificationFactory.newNewIssuesNotification(any(assigneeCacheType))).thenReturn(newIssuesNotificationMock);
when(notificationFactory.newMyNewIssuesNotification(any(assigneeCacheType))).thenReturn(myNewIssuesNotificationMock);
@Test
public void send_global_new_issues_notification() {
analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
- issueCache.newAppender().append(
- new DefaultIssue().setType(randomRuleType).setEffort(ISSUE_DURATION)
+ protoIssueCache.newAppender().append(
+ createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION)
.setCreationDate(new Date(ANALYSE_DATE)))
.close();
when(notificationService.hasProjectSubscribersForTypes(eq(PROJECT.getUuid()), any())).thenReturn(true);
Integer[] backDatedEfforts = IntStream.range(0, 1 + random.nextInt(10)).mapToObj(i -> 10 + random.nextInt(100)).toArray(Integer[]::new);
Duration expectedEffort = Duration.create(stream(efforts).mapToInt(i -> i).sum());
List<DefaultIssue> issues = concat(stream(efforts)
- .map(effort -> new DefaultIssue().setType(randomRuleType).setEffort(Duration.create(effort))
+ .map(effort -> createIssue().setType(randomRuleType).setEffort(Duration.create(effort))
.setCreationDate(new Date(ANALYSE_DATE))),
stream(backDatedEfforts)
- .map(effort -> new DefaultIssue().setType(randomRuleType).setEffort(Duration.create(effort))
+ .map(effort -> createIssue().setType(randomRuleType).setEffort(Duration.create(effort))
.setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS))))
.collect(toList());
shuffle(issues);
- DiskCache<DefaultIssue>.DiskAppender issueCache = this.issueCache.newAppender();
+ DiskCache.CacheAppender issueCache = this.protoIssueCache.newAppender();
issues.forEach(issueCache::append);
+ issueCache.close();
analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
@Test
public void do_not_send_global_new_issues_notification_if_issue_has_been_backdated() {
analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
- issueCache.newAppender().append(
- new DefaultIssue().setType(randomRuleType).setEffort(ISSUE_DURATION)
+ protoIssueCache.newAppender().append(
+ createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION)
.setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS)))
.close();
when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
public void send_global_new_issues_notification_on_branch() {
ComponentDto project = newPrivateProjectDto(newOrganizationDto());
ComponentDto branch = setUpBranch(project, BRANCH);
- issueCache.newAppender().append(
- new DefaultIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setCreationDate(new Date(ANALYSE_DATE))).close();
+ protoIssueCache.newAppender().append(
+ createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setCreationDate(new Date(ANALYSE_DATE))).close();
when(notificationService.hasProjectSubscribersForTypes(branch.uuid(), NOTIF_TYPES)).thenReturn(true);
analysisMetadataHolder.setProject(Project.from(project));
analysisMetadataHolder.setBranch(newBranch(BranchType.BRANCH));
public void do_not_send_global_new_issues_notification_on_pull_request() {
ComponentDto project = newPrivateProjectDto(newOrganizationDto());
ComponentDto branch = setUpBranch(project, PULL_REQUEST);
- issueCache.newAppender().append(
- new DefaultIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setCreationDate(new Date(ANALYSE_DATE))).close();
+ protoIssueCache.newAppender().append(
+ createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setCreationDate(new Date(ANALYSE_DATE))).close();
when(notificationService.hasProjectSubscribersForTypes(project.uuid(), NOTIF_TYPES)).thenReturn(true);
analysisMetadataHolder.setProject(Project.from(project));
analysisMetadataHolder.setBranch(newPullRequest());
verifyZeroInteractions(notificationService, newIssuesNotificationMock);
}
+ private DefaultIssue createIssue() {
+ return new DefaultIssue().setKey("k").setProjectKey("p").setStatus("OPEN").setProjectUuid("uuid").setComponentKey("c").setRuleKey(RuleKey.of("r", "r"));
+ }
+
@Test
public void do_not_send_global_new_issues_notification_on_branch_if_issue_has_been_backdated() {
ComponentDto project = newPrivateProjectDto(newOrganizationDto());
ComponentDto branch = setUpBranch(project, BRANCH);
- issueCache.newAppender().append(
- new DefaultIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS))).close();
+ protoIssueCache.newAppender().append(
+ createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS))).close();
when(notificationService.hasProjectSubscribersForTypes(branch.uuid(), NOTIF_TYPES)).thenReturn(true);
analysisMetadataHolder.setProject(Project.from(project));
analysisMetadataHolder.setBranch(newBranch(BranchType.BRANCH));
UserDto user = db.users().insertUser();
analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
- issueCache.newAppender().append(
- new DefaultIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setAssigneeUuid(user.getUuid())
- .setCreationDate(new Date(ANALYSE_DATE)))
+ protoIssueCache.newAppender().append(
+ createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setAssigneeUuid(user.getUuid()).setCreationDate(new Date(ANALYSE_DATE)))
.close();
when(notificationService.hasProjectSubscribersForTypes(eq(PROJECT.getUuid()), any())).thenReturn(true);
Integer[] assignedToOther = IntStream.range(0, 3).mapToObj(i -> 10).toArray(Integer[]::new);
List<DefaultIssue> issues = concat(stream(assigned)
- .map(effort -> new DefaultIssue().setType(randomRuleType).setEffort(Duration.create(effort))
+ .map(effort -> createIssue().setType(randomRuleType).setEffort(Duration.create(effort))
.setAssigneeUuid(perceval.getUuid())
+ .setNew(true)
.setCreationDate(new Date(ANALYSE_DATE))),
stream(assignedToOther)
- .map(effort -> new DefaultIssue().setType(randomRuleType).setEffort(Duration.create(effort))
+ .map(effort -> createIssue().setType(randomRuleType).setEffort(Duration.create(effort))
.setAssigneeUuid(arthur.getUuid())
+ .setNew(true)
.setCreationDate(new Date(ANALYSE_DATE))))
.collect(toList());
shuffle(issues);
- IssueCache issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
- DiskCache<DefaultIssue>.DiskAppender newIssueCache = issueCache.newAppender();
+ ProtoIssueCache protoIssueCache = new ProtoIssueCache(temp.newFile(), System2.INSTANCE);
+ DiskCache.CacheAppender newIssueCache = protoIssueCache.newAppender();
issues.forEach(newIssueCache::append);
-
+ newIssueCache.close();
analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
MyNewIssuesNotification myNewIssuesNotificationMock1 = createMyNewIssuesNotificationMock();
MyNewIssuesNotification myNewIssuesNotificationMock2 = createMyNewIssuesNotificationMock();
- when(notificationFactory.newMyNewIssuesNotification(any(assigneeCacheType)))
- .thenReturn(myNewIssuesNotificationMock1)
- .thenReturn(myNewIssuesNotificationMock2);
+ doReturn(myNewIssuesNotificationMock1).doReturn(myNewIssuesNotificationMock2).when(notificationFactory).newMyNewIssuesNotification(any(assigneeCacheType));
TestComputationStepContext context = new TestComputationStepContext();
- new SendIssueNotificationsStep(issueCache, treeRootHolder, notificationService, analysisMetadataHolder, notificationFactory, db.getDbClient())
+ new SendIssueNotificationsStep(protoIssueCache, treeRootHolder, notificationService, analysisMetadataHolder, notificationFactory, db.getDbClient())
.execute(context);
verify(notificationService).deliverEmails(ImmutableSet.of(myNewIssuesNotificationMock1, myNewIssuesNotificationMock2));
Integer[] backDatedEfforts = IntStream.range(0, 1 + random.nextInt(10)).mapToObj(i -> 10 + random.nextInt(100)).toArray(Integer[]::new);
Duration expectedEffort = Duration.create(stream(efforts).mapToInt(i -> i).sum());
List<DefaultIssue> issues = concat(stream(efforts)
- .map(effort -> new DefaultIssue().setType(randomRuleType).setEffort(Duration.create(effort))
+ .map(effort -> createIssue().setType(randomRuleType).setEffort(Duration.create(effort))
.setAssigneeUuid(user.getUuid())
.setCreationDate(new Date(ANALYSE_DATE))),
stream(backDatedEfforts)
- .map(effort -> new DefaultIssue().setType(randomRuleType).setEffort(Duration.create(effort))
+ .map(effort -> createIssue().setType(randomRuleType).setEffort(Duration.create(effort))
.setAssigneeUuid(user.getUuid())
.setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS))))
.collect(toList());
shuffle(issues);
- DiskCache<DefaultIssue>.DiskAppender issueCache = this.issueCache.newAppender();
+ DiskCache.CacheAppender issueCache = this.protoIssueCache.newAppender();
issues.forEach(issueCache::append);
+ issueCache.close();
analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
public void do_not_send_new_issues_notification_to_user_if_issue_is_backdated() {
analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
UserDto user = db.users().insertUser();
- issueCache.newAppender().append(
- new DefaultIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setAssigneeUuid(user.getUuid())
+ protoIssueCache.newAppender().append(
+ createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setAssigneeUuid(user.getUuid())
.setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS)))
.close();
when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
private DefaultIssue prepareIssue(long issueCreatedAt, UserDto user, ComponentDto project, ComponentDto file, RuleDefinitionDto ruleDefinitionDto, RuleType type) {
DefaultIssue issue = newIssue(ruleDefinitionDto, project, file).setType(type).toDefaultIssue()
.setNew(false).setChanged(true).setSendNotifications(true).setCreationDate(new Date(issueCreatedAt)).setAssigneeUuid(user.getUuid());
- issueCache.newAppender().append(issue).close();
+ protoIssueCache.newAppender().append(issue).close();
when(notificationService.hasProjectSubscribersForTypes(project.projectUuid(), NOTIF_TYPES)).thenReturn(true);
return issue;
}
.setChanged(true)
.setSendNotifications(true)
.setCreationDate(new Date(issueCreatedAt));
- issueCache.newAppender().append(issue).close();
+ protoIssueCache.newAppender().append(issue).close();
when(notificationService.hasProjectSubscribersForTypes(project.uuid(), NOTIF_TYPES)).thenReturn(true);
IssuesChangesNotification issuesChangesNotification = mock(IssuesChangesNotification.class);
when(notificationFactory.newIssuesChangesNotification(anySet(), anyMap())).thenReturn(issuesChangesNotification);
.mapToObj(i -> newIssue(ruleDefinitionDto, project, file).setKee("uuid_" + i).setType(randomTypeExceptHotspot).toDefaultIssue()
.setNew(false).setChanged(true).setSendNotifications(true).setAssigneeUuid(user.getUuid()))
.collect(toList());
- DiskCache<DefaultIssue>.DiskAppender diskAppender = issueCache.newAppender();
- issues.forEach(diskAppender::append);
- diskAppender.close();
+ DiskCache.CacheAppender cacheAppender = protoIssueCache.newAppender();
+ issues.forEach(cacheAppender::append);
+ cacheAppender.close();
analysisMetadataHolder.setProject(Project.from(project));
NewIssuesFactoryCaptor newIssuesFactoryCaptor = new NewIssuesFactoryCaptor(() -> mock(IssuesChangesNotification.class));
when(notificationFactory.newIssuesChangesNotification(anySet(), anyMap())).thenAnswer(newIssuesFactoryCaptor);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2020 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.ce.task.projectanalysis.util.cache;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.CloseableIterator;
-
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-
-public class DiskCacheTest {
-
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
-
- @Test
- public void write_and_read() throws Exception {
- DiskCache<String> cache = new DiskCache<>(temp.newFile(), System2.INSTANCE);
- try (CloseableIterator<String> traverse = cache.traverse()) {
- assertThat(traverse).isExhausted();
- }
-
- cache.newAppender()
- .append("foo")
- .append("bar")
- .close();
- try (CloseableIterator<String> traverse = cache.traverse()) {
- assertThat(traverse).toIterable().containsExactly("foo", "bar");
- }
- }
-
- @Test
- public void fail_if_file_is_not_writable() throws Exception {
- try {
- new DiskCache<>(temp.newFolder(), System2.INSTANCE);
- fail();
- } catch (IllegalStateException e) {
- assertThat(e).hasMessageContaining("Fail to write into file");
- }
- }
-
- @Test
- public void fail_to_serialize() throws Exception {
- class Unserializable implements Serializable {
- private void writeObject(ObjectOutputStream out) {
- throw new UnsupportedOperationException("expected error");
- }
- }
- DiskCache<Serializable> cache = new DiskCache<>(temp.newFile(), System2.INSTANCE);
- try {
- cache.newAppender().append(new Unserializable());
- fail();
- } catch (UnsupportedOperationException e) {
- assertThat(e).hasMessage("expected error");
- }
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.ce.task.projectanalysis.util.cache;
+
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.CloseableIterator;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class JavaSerializationDiskCacheTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Test
+ public void write_and_read() throws Exception {
+ DiskCache<String> cache = new JavaSerializationDiskCache<>(temp.newFile(), System2.INSTANCE);
+ try (CloseableIterator<String> traverse = cache.traverse()) {
+ assertThat(traverse).isExhausted();
+ }
+
+ cache.newAppender()
+ .append("foo")
+ .append("bar")
+ .close();
+ try (CloseableIterator<String> traverse = cache.traverse()) {
+ assertThat(traverse).toIterable().containsExactly("foo", "bar");
+ }
+ }
+
+ @Test
+ public void fail_if_file_is_not_writable() throws Exception {
+ try {
+ new JavaSerializationDiskCache<>(temp.newFolder(), System2.INSTANCE);
+ fail();
+ } catch (IllegalStateException e) {
+ assertThat(e).hasMessageContaining("Fail to write into file");
+ }
+ }
+
+ @Test
+ public void fail_to_serialize() throws Exception {
+ class Unserializable implements Serializable {
+ private void writeObject(ObjectOutputStream out) {
+ throw new UnsupportedOperationException("expected error");
+ }
+ }
+ DiskCache<Serializable> cache = new JavaSerializationDiskCache<>(temp.newFile(), System2.INSTANCE);
+ try {
+ cache.newAppender().append(new Unserializable());
+ fail();
+ } catch (UnsupportedOperationException e) {
+ assertThat(e).hasMessage("expected error");
+ }
+ }
+}
artifacts {
tests testJar
}
+
+jar {
+ // remove exclusion on proto files so that they can be included by other modules
+ setExcludes([])
+}
}
@Override
+ @CheckForNull
public String language() {
return language;
}
- public DefaultIssue setLanguage(String l) {
+ public DefaultIssue setLanguage(@Nullable String l) {
this.language = l;
return this;
}
}
public DefaultIssue setLine(@Nullable Integer l) {
- Preconditions.checkArgument(l == null || l > 0, "Line must be null or greater than zero (got %d)", l);
+ Preconditions.checkArgument(l == null || l > 0, "Line must be null or greater than zero (got %s)", l);
this.line = l;
return this;
}
return this;
}
+ public DefaultIssue setCurrentChangeWithoutAddChange(@Nullable FieldDiffs currentChange) {
+ this.currentChange = currentChange;
+ return this;
+ }
+
@CheckForNull
public FieldDiffs currentChange() {
return currentChange;
return false;
}
DefaultIssue that = (DefaultIssue) o;
- return !(key != null ? !key.equals(that.key) : (that.key != null));
+ return Objects.equals(key, that.key);
}
@Override
return this;
}
+ @CheckForNull
public String issueKey() {
return issueKey;
}
- public FieldDiffs setIssueKey(String issueKey) {
+ public FieldDiffs setIssueKey(@Nullable String issueKey) {
this.issueKey = issueKey;
return this;
}