import org.sonar.batch.protocol.output.issue.ReportIssue;
import org.sonar.server.computation.issue.IssueComputation;
+import javax.annotation.Nullable;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
private void browseComponent(ComputationContext context, ReportHelper helper, ReportComponent component) {
Iterable<ReportIssue> reportIssues = helper.getIssues(component.batchId());
- browseComponentIssues(context, reportIssues);
+ browseComponentIssues(context, component, reportIssues);
for (ReportComponent child : component.children()) {
browseComponent(context, helper, child);
}
}
- private void browseComponentIssues(final ComputationContext context, Iterable<ReportIssue> reportIssues) {
- issueComputation.processComponentIssues(Iterables.transform(reportIssues, new Function<ReportIssue, DefaultIssue>() {
+ private void browseComponentIssues(final ComputationContext context, ReportComponent component, Iterable<ReportIssue> reportIssues) {
+ issueComputation.processComponentIssues(component.uuid(), Iterables.transform(reportIssues, new Function<ReportIssue, DefaultIssue>() {
@Override
public DefaultIssue apply(ReportIssue input) {
return toIssue(context, input);
private DefaultIssue toIssue(ComputationContext context, ReportIssue issue) {
DefaultIssue defaultIssue = new DefaultIssue();
defaultIssue.setKey(issue.key());
- setComponentId(defaultIssue, context.getComponentByBatchId(issue.componentBatchId()));
+ ReportComponent component = context.getComponentByBatchId(issue.componentBatchId());
+ setComponent(defaultIssue, component);
defaultIssue.setRuleKey(RuleKey.of(issue.ruleRepo(), issue.ruleKey()));
defaultIssue.setSeverity(issue.severity());
defaultIssue.setManualSeverity(issue.isManualSeverity());
return issue;
}
- private DefaultIssue setComponentId(DefaultIssue issue, ReportComponent component) {
+ private DefaultIssue setComponent(DefaultIssue issue, @Nullable ReportComponent component) {
if (component != null) {
- issue.setComponentId((long) component.id());
+ issue.setComponentId((long)component.id());
+ issue.setComponentUuid(component.uuid());
}
return issue;
}
- private DefaultIssue setDebt(DefaultIssue issue, Long debt) {
+ private DefaultIssue setDebt(DefaultIssue issue, @Nullable Long debt) {
if (debt != null) {
issue.setDebt(Duration.create(debt));
}
package org.sonar.server.computation;
import org.sonar.core.issue.db.UpdateConflictResolver;
+import org.sonar.server.computation.issue.IssueCache;
import org.sonar.server.computation.issue.IssueComputation;
-import org.sonar.server.computation.issue.FinalIssues;
import org.sonar.server.computation.issue.RuleCache;
import org.sonar.server.computation.issue.RuleCacheLoader;
+import org.sonar.server.computation.issue.ScmAccountCache;
+import org.sonar.server.computation.issue.ScmAccountCacheLoader;
+import org.sonar.server.computation.issue.SourceLinesCache;
import org.sonar.server.computation.step.ComputationSteps;
import java.util.Arrays;
AnalysisReportService.class,
// issues
+ ScmAccountCacheLoader.class,
+ ScmAccountCache.class,
+ SourceLinesCache.class,
IssueComputation.class,
RuleCache.class,
RuleCacheLoader.class,
- FinalIssues.class,
+ IssueCache.class,
UpdateConflictResolver.class);
}
}
try {
report.setFinishedAt(System2.INSTANCE.now());
activityService.write(session, Activity.Type.ANALYSIS_REPORT, new AnalysisReportLog(report, project));
+ session.commit();
} finally {
MyBatis.closeQuietly(session);
}
+++ /dev/null
-/*
- * 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.issue;
-
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.TempFolder;
-import org.sonar.server.util.cache.DiskCache;
-
-import java.io.IOException;
-
-/**
- * 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 FinalIssues extends DiskCache<DefaultIssue> {
-
- public FinalIssues(TempFolder tempFolder, System2 system2) throws IOException {
- super(tempFolder.newFile("issues", ".dat"), system2);
- }
-
-}
--- /dev/null
+/*
+ * 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.issue;
+
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.TempFolder;
+import org.sonar.server.util.cache.DiskCache;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * 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> {
+
+ public IssueCache(TempFolder tempFolder, System2 system2) throws IOException {
+ super(tempFolder.newFile("issues", ".dat"), system2);
+ }
+
+ IssueCache(File file, System2 system2) {
+ super(file, system2);
+ }
+}
public class IssueComputation {
private final RuleCache ruleCache;
+ private final ScmAccountCache scmAccountCache;
+ private final SourceLinesCache linesCache;
private final DiskCache<DefaultIssue>.DiskAppender finalIssuesAppender;
- public IssueComputation(RuleCache ruleCache, FinalIssues finalIssues) {
+ public IssueComputation(RuleCache ruleCache, SourceLinesCache linesCache, ScmAccountCache scmAccountCache,
+ IssueCache issueCache) {
this.ruleCache = ruleCache;
- this.finalIssuesAppender = finalIssues.newAppender();
+ this.linesCache = linesCache;
+ this.scmAccountCache = scmAccountCache;
+ this.finalIssuesAppender = issueCache.newAppender();
}
- public void processComponentIssues(Iterable<DefaultIssue> issues) {
+ public void processComponentIssues(String componentUuid, Iterable<DefaultIssue> issues) {
+ linesCache.init(componentUuid);
for (DefaultIssue issue : issues) {
if (issue.isNew()) {
guessAuthor(issue);
}
finalIssuesAppender.append(issue);
}
+ linesCache.clear();
}
public void afterReportProcessing() {
}
private void guessAuthor(DefaultIssue issue) {
- // TODO
+ if (issue.line() != null) {
+ issue.setAuthorLogin(linesCache.lineAuthor(issue.line()));
+ }
}
private void autoAssign(DefaultIssue issue) {
- // TODO
+ String scmAccount = issue.authorLogin();
+ if (scmAccount != null) {
+ issue.setAssignee(scmAccountCache.getNullable(scmAccount));
+ }
}
private void copyRuleTags(DefaultIssue issue) {
@CheckForNull
public String ruleName(RuleKey key) {
- RuleDto rule = get(key);
+ RuleDto rule = getNullable(key);
return rule != null ? rule.getName() : null;
}
}
import org.sonar.api.rule.RuleKey;
import org.sonar.core.persistence.DbSession;
import org.sonar.core.rule.RuleDto;
-import org.sonar.server.util.cache.CacheLoader;
import org.sonar.server.db.DbClient;
+import org.sonar.server.util.cache.CacheLoader;
import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
import java.util.Map;
public class RuleCacheLoader implements CacheLoader<RuleKey, RuleDto> {
@Override
public Map<RuleKey, RuleDto> loadAll(Collection<? extends RuleKey> keys) {
- Map<RuleKey, RuleDto> result = new HashMap<>();
- DbSession session = dbClient.openSession(false);
- try {
- List<RuleDto> dtos = dbClient.ruleDao().getByKeys(session, (Collection<RuleKey>) keys);
- for (RuleDto dto : dtos) {
- result.put(dto.getKey(), dto);
- }
- return result;
- } finally {
- session.close();
- }
+ throw new UnsupportedOperationException("See RuleDao");
}
}
--- /dev/null
+/*
+ * 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.issue;
+
+import org.sonar.server.util.cache.MemoryCache;
+
+/**
+ * Cache of dictionary {SCM account -> SQ user login}
+ */
+public class ScmAccountCache extends MemoryCache<String,String> {
+ public ScmAccountCache(ScmAccountCacheLoader loader) {
+ super(loader);
+ }
+}
--- /dev/null
+/*
+ * 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.issue;
+
+import org.sonar.server.user.index.UserDoc;
+import org.sonar.server.user.index.UserIndex;
+import org.sonar.server.util.cache.CacheLoader;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Loads the association between a SCM account and a SQ user
+ */
+public class ScmAccountCacheLoader implements CacheLoader<String,String> {
+
+ private final UserIndex index;
+
+ public ScmAccountCacheLoader(UserIndex index) {
+ this.index = index;
+ }
+
+ @Override
+ public String load(String scmAccount) {
+ UserDoc user = index.getNullableByScmAccount(scmAccount);
+ return user != null ? user.login() : null;
+ }
+
+ @Override
+ public Map<String, String> loadAll(Collection<? extends String> scmAccounts) {
+ throw new UnsupportedOperationException("Loading by multiple scm accounts is not supported yet");
+ }
+}
--- /dev/null
+/*
+ * 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.issue;
+
+import com.google.common.base.Function;
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVParser;
+import org.apache.commons.csv.CSVRecord;
+import org.apache.commons.io.IOUtils;
+import org.sonar.core.source.db.FileSourceDto;
+import org.sonar.server.db.DbClient;
+
+import javax.annotation.CheckForNull;
+
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Cache of the lines of the currently processed file. Only a single file
+ * is kept in memory at a time. Moreover data is loaded on demand to avoid
+ * useless db trips.
+ * <p/>
+ * It assumes that db table FILE_SOURCES is up-to-date before using this
+ * cache.
+ */
+public class SourceLinesCache {
+
+ private final DbClient dbClient;
+ private final List<String> authors = new ArrayList<>();
+ private final FileDataParser parserFunction = new FileDataParser();
+ private boolean loaded = false;
+ private String currentFileUuid = null;
+
+ public SourceLinesCache(DbClient dbClient) {
+ this.dbClient = dbClient;
+ }
+
+ /**
+ * Marks the currently processed component
+ */
+ void init(String fileUuid) {
+ loaded = false;
+ currentFileUuid = fileUuid;
+ }
+
+ /**
+ * Last committer of the line, can be null.
+ * @param lineId starts at 1
+ */
+ @CheckForNull
+ public String lineAuthor(int lineId) {
+ loadIfNeeded();
+ if (lineId <= authors.size()) {
+ return authors.get(lineId - 1);
+ }
+ return null;
+ }
+
+ /**
+ * Load only on demand, to avoid useless db requests on files without any new issues
+ */
+ private void loadIfNeeded() {
+ if (!loaded) {
+ dbClient.fileSourceDao().readDataStream(currentFileUuid, parserFunction);
+ loaded = true;
+ }
+ }
+
+ /**
+ * Makes cache eligible to GC
+ */
+ public void clear() {
+ authors.clear();
+ }
+
+ /**
+ * Number of lines in cache of the current file
+ */
+ int countLines() {
+ return authors.size();
+ }
+
+ class FileDataParser implements Function<Reader, Void> {
+ @Override
+ public Void apply(Reader input) {
+ CSVParser csvParser = null;
+ try {
+ csvParser = new CSVParser(input, CSVFormat.DEFAULT);
+ authors.clear();
+ for (CSVRecord csvRecord : csvParser) {
+ // do not keep all fields in memory
+ String author = csvRecord.get(FileSourceDto.CSV_INDEX_SCM_AUTHOR);
+ authors.add(author);
+ }
+ return null;
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to parse CSV data", e);
+ } finally {
+ IOUtils.closeQuietly(csvParser);
+ }
+ }
+ }
+}
import org.sonar.core.persistence.DbSession;
import org.sonar.core.persistence.MyBatis;
import org.sonar.server.computation.ComputationContext;
-import org.sonar.server.computation.issue.FinalIssues;
+import org.sonar.server.computation.issue.IssueCache;
import org.sonar.server.computation.issue.RuleCache;
import org.sonar.server.db.DbClient;
import org.sonar.server.util.CloseableIterator;
private final System2 system2;
private final UpdateConflictResolver conflictResolver;
private final RuleCache ruleCache;
- private final FinalIssues finalIssues;
+ private final IssueCache issueCache;
public PersistIssuesStep(DbClient dbClient, System2 system2, UpdateConflictResolver conflictResolver,
- RuleCache ruleCache, FinalIssues finalIssues) {
+ RuleCache ruleCache, IssueCache issueCache) {
this.dbClient = dbClient;
this.system2 = system2;
this.conflictResolver = conflictResolver;
this.ruleCache = ruleCache;
- this.finalIssues = finalIssues;
+ this.issueCache = issueCache;
}
@Override
IssueChangeMapper changeMapper = session.getMapper(IssueChangeMapper.class);
int count = 0;
- CloseableIterator<DefaultIssue> issues = finalIssues.traverse();
+ CloseableIterator<DefaultIssue> issues = issueCache.traverse();
try {
while (issues.hasNext()) {
DefaultIssue issue = issues.next();
import org.sonar.api.utils.DateUtils;
import org.sonar.core.component.ComponentDto;
import org.sonar.server.computation.ComputationContext;
-import org.sonar.server.computation.issue.FinalIssues;
+import org.sonar.server.computation.issue.IssueCache;
import org.sonar.server.computation.issue.RuleCache;
import org.sonar.server.issue.notification.IssueNotifications;
import org.sonar.server.util.CloseableIterator;
*/
public class SendIssueNotificationsStep implements ComputationStep {
- private final FinalIssues finalIssues;
+ private final IssueCache issueCache;
private final RuleCache rules;
private final IssueNotifications service;
- public SendIssueNotificationsStep(FinalIssues finalIssues, RuleCache rules,
+ public SendIssueNotificationsStep(IssueCache issueCache, RuleCache rules,
IssueNotifications service) {
- this.finalIssues = finalIssues;
+ this.issueCache = issueCache;
this.rules = rules;
this.service = service;
}
@Override
public void execute(ComputationContext context) {
NewIssuesStatistics newIssuesStatistics = new NewIssuesStatistics();
- CloseableIterator<DefaultIssue> issues = finalIssues.traverse();
+ CloseableIterator<DefaultIssue> issues = issueCache.traverse();
try {
while (issues.hasNext()) {
DefaultIssue issue = issues.next();
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.sonar.api.utils.DateUtils;
+import org.sonar.core.source.db.FileSourceDto;
import org.sonar.server.db.DbClient;
import org.sonar.server.db.ResultSetIterator;
import org.sonar.server.db.migrations.SqlUtil;
doc.setFileUuid(fileUuid);
doc.setLine(line);
doc.setUpdateDate(updatedDate);
- doc.setScmRevision(csvRecord.get(0));
- doc.setScmAuthor(csvRecord.get(1));
- doc.setScmDate(DateUtils.parseDateTimeQuietly(csvRecord.get(2)));
+ doc.setScmRevision(csvRecord.get(FileSourceDto.CSV_INDEX_SCM_REVISION));
+ doc.setScmAuthor(csvRecord.get(FileSourceDto.CSV_INDEX_SCM_AUTHOR));
+ doc.setScmDate(DateUtils.parseDateTimeQuietly(csvRecord.get(FileSourceDto.CSV_INDEX_SCM_DATE)));
// UT
- doc.setUtLineHits(parseIntegerFromRecord(csvRecord.get(3)));
- doc.setUtConditions(parseIntegerFromRecord(csvRecord.get(4)));
- doc.setUtCoveredConditions(parseIntegerFromRecord(csvRecord.get(5)));
+ doc.setUtLineHits(parseIntegerFromRecord(csvRecord.get(FileSourceDto.CSV_INDEX_UT_LINE_HITS)));
+ doc.setUtConditions(parseIntegerFromRecord(csvRecord.get(FileSourceDto.CSV_INDEX_UT_CONDITIONS)));
+ doc.setUtCoveredConditions(parseIntegerFromRecord(csvRecord.get(FileSourceDto.CSV_INDEX_UT_COVERED_CONDITIONS)));
// IT
- doc.setItLineHits(parseIntegerFromRecord(csvRecord.get(6)));
- doc.setItConditions(parseIntegerFromRecord(csvRecord.get(7)));
- doc.setItCoveredConditions(parseIntegerFromRecord(csvRecord.get(8)));
+ doc.setItLineHits(parseIntegerFromRecord(csvRecord.get(FileSourceDto.CSV_INDEX_IT_LINE_HITS)));
+ doc.setItConditions(parseIntegerFromRecord(csvRecord.get(FileSourceDto.CSV_INDEX_IT_CONDITIONS)));
+ doc.setItCoveredConditions(parseIntegerFromRecord(csvRecord.get(FileSourceDto.CSV_INDEX_IT_COVERED_CONDITIONS)));
// OVERALL
- doc.setOverallLineHits(parseIntegerFromRecord(csvRecord.get(9)));
- doc.setOverallConditions(parseIntegerFromRecord(csvRecord.get(10)));
- doc.setOverallCoveredConditions(parseIntegerFromRecord(csvRecord.get(11)));
- doc.setHighlighting(csvRecord.get(12));
- doc.setSymbols(csvRecord.get(13));
-
- doc.setDuplications(parseDuplications(csvRecord.get(14)));
- doc.setSource(csvRecord.get(csvRecord.size() - 1));
+ doc.setOverallLineHits(parseIntegerFromRecord(csvRecord.get(FileSourceDto.CSV_INDEX_OVERALL_LINE_HITS)));
+ doc.setOverallConditions(parseIntegerFromRecord(csvRecord.get(FileSourceDto.CSV_INDEX_OVERALL_CONDITIONS)));
+ doc.setOverallCoveredConditions(parseIntegerFromRecord(csvRecord.get(FileSourceDto.CSV_INDEX_OVERALL_COVERED_CONDITIONS)));
+
+ doc.setHighlighting(csvRecord.get(FileSourceDto.CSV_INDEX_HIGHLIGHTING));
+ doc.setSymbols(csvRecord.get(FileSourceDto.CSV_INDEX_SYMBOLS));
+
+ doc.setDuplications(parseDuplications(csvRecord.get(FileSourceDto.CSV_INDEX_DUPLICATIONS)));
+ doc.setSource(csvRecord.get(FileSourceDto.CSV_INDEX_SOURCE));
result.addLine(doc);
import org.sonar.server.es.NewIndex;
/**
- * Definition of ES index "issues", including settings and fields.
+ * Definition of ES index "users", including settings and fields.
*/
public class UserIndexDefinition implements IndexDefinition {
// else keep defaults (one shard)
}
- // type "users"
+ // type "user"
NewIndex.NewIndexType mapping = index.createType(TYPE_USER);
mapping.setAttribute("_id", ImmutableMap.of("path", FIELD_LOGIN));
mapping.stringFieldBuilder(FIELD_LOGIN).build();
*/
package org.sonar.server.util.cache;
+import org.sonar.server.exceptions.NotFoundException;
+
import javax.annotation.CheckForNull;
import java.util.ArrayList;
import java.util.HashMap;
public V get(K key) {
V value = getNullable(key);
if (value == null) {
- throw new IllegalArgumentException("Not found: " + key);
+ throw new NotFoundException("Not found: " + key);
}
return value;
}
+ /**
+ * Get values associated with keys. All the requested keys are included
+ * in the Map result. Value is null if the key is not found in cache.
+ */
public Map<K, V> getAll(Iterable<K> keys) {
List<K> missingKeys = new ArrayList<>();
Map<K, V> result = new HashMap<>();
Map<K, V> missingValues = loader.loadAll(missingKeys);
map.putAll(missingValues);
result.putAll(missingValues);
+ for (K missingKey : missingKeys) {
+ if (!map.containsKey(missingKey)) {
+ map.put(missingKey, null);
+ result.put(missingKey, null);
+ }
+ }
}
return result;
}
--- /dev/null
+/*
+ * 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.issue;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterators;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.System2;
+import org.sonar.core.rule.RuleDto;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class IssueComputationTest {
+
+ public static final RuleKey RULE_KEY = RuleKey.of("squid", "R1");
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ // inputs
+ RuleCache ruleCache = mock(RuleCache.class);
+ SourceLinesCache lineCache = mock(SourceLinesCache.class);
+ ScmAccountCache scmAccountCache = mock(ScmAccountCache.class);
+ DefaultIssue issue = new DefaultIssue().setRuleKey(RULE_KEY).setKey("ISSUE_A");
+ RuleDto rule = new RuleDto().setRepositoryKey(RULE_KEY.repository()).setRuleKey(RULE_KEY.rule());
+
+ // output
+ IssueCache issueCache;
+
+ IssueComputation sut;
+
+ @Before
+ public void setUp() throws IOException {
+ when(ruleCache.get(RULE_KEY)).thenReturn(rule);
+ issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
+ sut = new IssueComputation(ruleCache, lineCache, scmAccountCache, issueCache);
+ }
+
+ @Test
+ public void store_issues_on_disk() throws Exception {
+ process();
+
+ assertThat(Iterators.getOnlyElement(issueCache.traverse()).key()).isEqualTo("ISSUE_A");
+ }
+
+ @Test
+ public void copy_rule_tags_on_new_issues() throws Exception {
+ issue.setNew(true);
+ rule.setTags(ImmutableSet.of("bug", "performance"));
+ rule.setSystemTags(ImmutableSet.of("blocker"));
+
+ process();
+
+ assertThat(Iterators.getOnlyElement(issueCache.traverse()).tags()).containsOnly("blocker", "bug", "performance");
+ }
+
+ @Test
+ public void do_not_copy_rule_tags_on_existing_issues() throws Exception {
+ issue.setNew(false);
+ rule.setTags(ImmutableSet.of("bug", "performance"));
+ rule.setSystemTags(ImmutableSet.of("blocker"));
+
+ process();
+
+ assertThat(Iterators.getOnlyElement(issueCache.traverse()).tags()).isEmpty();
+ }
+
+ @Test
+ public void guess_author_of_new_issues() throws Exception {
+ issue.setNew(true);
+ issue.setLine(3);
+ when(lineCache.lineAuthor(3)).thenReturn("charlie");
+
+ process();
+
+ assertThat(Iterators.getOnlyElement(issueCache.traverse()).authorLogin()).isEqualTo("charlie");
+ }
+
+ @Test
+ public void do_not_fail_if_missing_author_for_new_issues() throws Exception {
+ issue.setNew(true);
+ issue.setLine(3);
+ when(lineCache.lineAuthor(3)).thenReturn(null);
+
+ process();
+
+ assertThat(Iterators.getOnlyElement(issueCache.traverse()).authorLogin()).isNull();
+ }
+
+ @Test
+ public void do_not_guess_author_of_existing_issues() throws Exception {
+ issue.setNew(false);
+ issue.setLine(3);
+ when(lineCache.lineAuthor(3)).thenReturn("charlie");
+
+ process();
+
+ assertThat(Iterators.getOnlyElement(issueCache.traverse()).authorLogin()).isNull();
+ }
+
+ @Test
+ public void auto_assign_new_issues() throws Exception {
+ issue.setNew(true);
+ issue.setAuthorLogin("charlie");
+ when(scmAccountCache.getNullable("charlie")).thenReturn("char.lie");
+
+ process();
+
+ assertThat(Iterators.getOnlyElement(issueCache.traverse()).assignee()).isEqualTo("char.lie");
+ }
+
+ @Test
+ public void do_not_auto_assign_existing_issues() throws Exception {
+ issue.setNew(false);
+ issue.setAuthorLogin("charlie");
+ when(scmAccountCache.getNullable("charlie")).thenReturn("char.lie");
+
+ process();
+
+ assertThat(Iterators.getOnlyElement(issueCache.traverse()).assignee()).isNull();
+ }
+
+ private void process() {
+ sut.processComponentIssues("FILE_A", Arrays.asList(issue));
+ sut.afterReportProcessing();
+ }
+}
--- /dev/null
+/*
+ * 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.issue;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.persistence.DbTester;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.rule.db.RuleDao;
+
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class RuleCacheLoaderTest {
+
+ @Rule
+ public DbTester dbTester = new DbTester();
+
+ @Test
+ public void load_by_key() throws Exception {
+ dbTester.prepareDbUnit(getClass(), "shared.xml");
+ DbClient dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), new RuleDao());
+ RuleCacheLoader loader = new RuleCacheLoader(dbClient);
+
+ assertThat(loader.load(RuleKey.of("squid", "R001")).getName()).isEqualTo("Rule One");
+ assertThat(loader.load(RuleKey.of("squid", "MISSING"))).isNull();
+ }
+
+ @Test
+ public void load_by_keys_is_not_supported() throws Exception {
+ DbClient dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), new RuleDao());
+ RuleCacheLoader loader = new RuleCacheLoader(dbClient);
+ try {
+ loader.loadAll(Collections.<RuleKey>emptyList());
+ fail();
+ } catch (UnsupportedOperationException e) {
+ // see RuleDao#getByKeys()
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.issue;
+
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.core.rule.RuleDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class RuleCacheTest {
+
+ @Test
+ public void ruleName() throws Exception {
+ RuleCacheLoader loader = mock(RuleCacheLoader.class);
+ when(loader.load(RuleKey.of("squid", "R002"))).thenReturn(new RuleDto().setName("Rule Two"));
+ RuleCache cache = new RuleCache(loader);
+ assertThat(cache.ruleName(RuleKey.of("squid", "R001"))).isNull();
+ assertThat(cache.ruleName(RuleKey.of("squid", "R002"))).isEqualTo("Rule Two");
+ }
+}
--- /dev/null
+/*
+ * 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.issue;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.Settings;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.user.index.UserIndex;
+import org.sonar.server.user.index.UserIndexDefinition;
+
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class ScmAccountCacheLoaderTest {
+
+ @Rule
+ public EsTester esTester = new EsTester().addDefinitions(new UserIndexDefinition(new Settings()));
+
+ @Test
+ public void load_login_for_scm_account() throws Exception {
+ esTester.putDocuments("users", "user", getClass(), "user1.json");
+
+ UserIndex index = new UserIndex(esTester.client());
+ ScmAccountCacheLoader loader = new ScmAccountCacheLoader(index);
+
+ assertThat(loader.load("missing")).isNull();
+ assertThat(loader.load("jesuis@charlie.com")).isEqualTo("charlie");
+ }
+
+ @Test
+ public void load_by_multiple_scm_accounts_is_not_supported_yet() throws Exception {
+ UserIndex index = new UserIndex(esTester.client());
+ ScmAccountCacheLoader loader = new ScmAccountCacheLoader(index);
+ try {
+ loader.loadAll(Collections.<String>emptyList());
+ fail();
+ } catch (UnsupportedOperationException ignored) {
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.issue;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.sonar.core.persistence.DbTester;
+import org.sonar.core.source.db.FileSourceDao;
+import org.sonar.server.db.DbClient;
+import org.sonar.test.DbTests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Category(DbTests.class)
+public class SourceLinesCacheTest {
+
+ @Rule
+ public DbTester dbTester = new DbTester();
+
+ @Test
+ public void load_data() throws Exception {
+ dbTester.prepareDbUnit(getClass(), "load_data.xml");
+ DbClient dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), new FileSourceDao(dbTester.myBatis()));
+ SourceLinesCache cache = new SourceLinesCache(dbClient);
+ cache.init("FILE_A");
+
+ // load data on demand -> still nothing in cache
+ assertThat(cache.countLines()).isEqualTo(0);
+
+ assertThat(cache.lineAuthor(1)).isEqualTo("charlie");
+ assertThat(cache.lineAuthor(2)).isEqualTo("cabu");
+ assertThat(cache.lineAuthor(3)).isNull();
+ assertThat(cache.countLines()).isEqualTo(2);
+
+ cache.clear();
+ assertThat(cache.countLines()).isEqualTo(0);
+ }
+}
import com.google.common.collect.ImmutableMap;
import org.junit.Test;
+import org.sonar.server.exceptions.NotFoundException;
import java.util.Arrays;
import java.util.HashMap;
try {
cache.get("not_exists");
fail();
- } catch (IllegalArgumentException e) {
+ } catch (NotFoundException e) {
assertThat(e).hasMessage("Not found: not_exists");
}
}
@Test
public void getAllNullable() throws Exception {
+ // ask for 3 keys but only 2 are available in backed (third key is missing)
List<String> keys = Arrays.asList("one", "two", "three");
Map<String, String> values = new HashMap<>();
values.put("one", "un");
values.put("two", "deux");
- values.put("three", null);
when(loader.loadAll(keys)).thenReturn(values);
assertThat(cache.getAll(keys))
.hasSize(3)
.containsEntry("two", "deux")
.containsEntry("three", null);
+ // ask for 4 keys. Only a single one was never loaded. The 3 others are kept from cache
when(loader.loadAll(Arrays.asList("four"))).thenReturn(ImmutableMap.of("four", "quatre"));
- assertThat(cache.getAll(Arrays.asList("one", "two", "four")))
- .hasSize(3)
+ assertThat(cache.getAll(Arrays.asList("one", "two", "three", "four")))
+ .hasSize(4)
.containsEntry("one", "un")
.containsEntry("two", "deux")
+ .containsEntry("three", null)
.containsEntry("four", "quatre");
verify(loader, times(2)).loadAll(anyCollection());
}
--- /dev/null
+<dataset>
+ <rules id="1" name="Rule One" plugin_name="squid" plugin_rule_key="R001"
+ plugin_config_key="[null]" description="[null]" priority="4"
+ status="READY"
+ is_template="[false]" template_id="[null]"
+ tags="[null]" system_tags="[null]"/>
+
+ <rules id="2" name="Rule Two" plugin_name="squid" plugin_rule_key="R002"
+ plugin_config_key="[null]" description="[null]" priority="4"
+ status="READY"
+ is_template="[false]" template_id="[null]"
+ tags="[null]" system_tags="[null]"/>
+
+</dataset>
--- /dev/null
+{
+ "login": "charlie",
+ "name": "Charlie",
+ "email": "charlie@hebdo.com",
+ "active": true,
+ "scmAccounts": ["charlie", "jesuis@charlie.com"],
+ "createdAt": 1500000000000,
+ "updatedAt": 1500000000000
+}
--- /dev/null
+<dataset>
+ <file_sources id="1" file_uuid="FILE_A" project_uuid="PROJECT_A"
+ data=",charlie,,,,,,,,,,,,,,first line ,cabu,,,,,,,,,,,,,,second line "
+ line_hashes="8d7b3d6b83c0a517eac07e1aac94b773 9a0364b9e99bb480dd25e1f0284c8555"
+ data_hash="0263047cd758c68c27683625f072f010"
+ src_hash="123456"
+ created_at="1412952242000" updated_at="1412952242000"/>
+</dataset>
private int snapshotId;
private String path;
private String name;
+ private String uuid;
private Type type;
// Only for files
private Boolean isTest;
return path;
}
+ public ReportComponent setUuid(String s) {
+ this.uuid = s;
+ return this;
+ }
+
+ public String uuid() {
+ return uuid;
+ }
+
public ReportComponent setName(@Nullable String name) {
this.name = name;
return this;
.setId(r.getId())
.setName(getName(r))
.setPath(r.getPath())
+ .setUuid(r.getUuid())
.setType(getType(r))
.setLanguageKey(getLanguageKey(r))
.setTest(isTest(r));
package org.sonar.core.source.db;
+import com.google.common.base.Function;
+import org.apache.commons.dbutils.DbUtils;
+import org.apache.commons.io.IOUtils;
import org.sonar.api.BatchComponent;
import org.sonar.api.ServerComponent;
import org.sonar.core.persistence.DaoComponent;
import javax.annotation.CheckForNull;
+import java.io.Reader;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
public class FileSourceDao implements BatchComponent, ServerComponent, DaoComponent {
private final MyBatis mybatis;
}
}
+ public <T> void readDataStream(String fileUuid, Function<Reader, T> function) {
+ DbSession dbSession = mybatis.openSession(false);
+ Connection connection = dbSession.getConnection();
+ PreparedStatement pstmt = null;
+ ResultSet rs = null;
+ Reader reader = null;
+ try {
+ pstmt = connection.prepareStatement("select data from file_sources where file_uuid = ?");
+ pstmt.setString(1, fileUuid);
+ rs = pstmt.executeQuery();
+ if (rs.next()) {
+ reader = rs.getCharacterStream(1);
+ function.apply(reader);
+ }
+ } catch (SQLException e) {
+ throw new IllegalStateException("Fail to read FILE_SOURCES.DATA of file " + fileUuid, e);
+ } finally {
+ IOUtils.closeQuietly(reader);
+ DbUtils.closeQuietly(connection, pstmt, rs);
+ MyBatis.closeQuietly(dbSession);
+ }
+ }
+
public void insert(FileSourceDto dto) {
DbSession session = mybatis.openSession(false);
try {
import javax.annotation.Nullable;
public class FileSourceDto {
+
+ public static final int CSV_INDEX_SCM_REVISION = 0;
+ public static final int CSV_INDEX_SCM_AUTHOR = 1;
+ public static final int CSV_INDEX_SCM_DATE = 2;
+ public static final int CSV_INDEX_UT_LINE_HITS = 3;
+ public static final int CSV_INDEX_UT_CONDITIONS = 4;
+ public static final int CSV_INDEX_UT_COVERED_CONDITIONS = 5;
+ public static final int CSV_INDEX_IT_LINE_HITS = 6;
+ public static final int CSV_INDEX_IT_CONDITIONS = 7;
+ public static final int CSV_INDEX_IT_COVERED_CONDITIONS = 8;
+ public static final int CSV_INDEX_OVERALL_LINE_HITS = 9;
+ public static final int CSV_INDEX_OVERALL_CONDITIONS = 10;
+ public static final int CSV_INDEX_OVERALL_COVERED_CONDITIONS = 11;
+ public static final int CSV_INDEX_HIGHLIGHTING = 12;
+ public static final int CSV_INDEX_SYMBOLS = 13;
+ public static final int CSV_INDEX_DUPLICATIONS = 14;
+ public static final int CSV_INDEX_SOURCE = 15;
+
private Long id;
private String projectUuid;
private String fileUuid;