From: Teryk Bellahsene Date: Thu, 18 Dec 2014 18:11:14 +0000 (+0100) Subject: SONAR-5911 persist issues on server side X-Git-Tag: latest-silver-master-#65~416 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=0315dec02e79f8fc3f59d4e7d83710b02dc6735b;p=sonarqube.git SONAR-5911 persist issues on server side --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/AnalysisReportService.java b/server/sonar-server/src/main/java/org/sonar/server/computation/AnalysisReportService.java index 07389ebab96..80ba861239f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/AnalysisReportService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/AnalysisReportService.java @@ -21,29 +21,64 @@ package org.sonar.server.computation; import com.google.common.annotations.VisibleForTesting; +import com.google.gson.Gson; +import com.google.gson.stream.JsonReader; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.ServerComponent; +import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.api.issue.internal.FieldDiffs; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.utils.Duration; +import org.sonar.api.utils.KeyValueFormat; +import org.sonar.batch.protocol.GsonHelper; +import org.sonar.batch.protocol.output.issue.ReportIssue; +import org.sonar.batch.protocol.output.resource.ReportComponent; +import org.sonar.batch.protocol.output.resource.ReportComponents; import org.sonar.core.computation.db.AnalysisReportDto; +import org.sonar.core.issue.db.IssueStorage; import org.sonar.core.persistence.DbSession; import org.sonar.server.db.DbClient; import javax.annotation.Nullable; -import java.io.File; -import java.io.IOException; +import java.io.*; +import java.util.ArrayList; +import java.util.List; public class AnalysisReportService implements ServerComponent { private static final Logger LOG = LoggerFactory.getLogger(AnalysisReportService.class); + private static final int MAX_ISSUES_SIZE = 1000; + private final ComputeEngineIssueStorageFactory issueStorageFactory; private final DbClient dbClient; + private final Gson gson; - public AnalysisReportService(DbClient dbClient) { + public AnalysisReportService(DbClient dbClient, ComputeEngineIssueStorageFactory issueStorageFactory) { + this.issueStorageFactory = issueStorageFactory; this.dbClient = dbClient; + gson = GsonHelper.create(); } public void digest(DbSession session, ComputeEngineContext context) { decompress(session, context); + loadResources(context); + saveIssues(context); + } + + @VisibleForTesting + void loadResources(ComputeEngineContext context) { + File file = new File(context.getReportDirectory(), "components.json"); + + try { + InputStream resourcesStream = new FileInputStream(file); + String json = IOUtils.toString(resourcesStream); + ReportComponents reportComponents = ReportComponents.fromJson(json); + context.addResources(reportComponents); + } catch (IOException e) { + throw new IllegalStateException("Failed to read issues", e); + } } @VisibleForTesting @@ -54,7 +89,77 @@ public class AnalysisReportService implements ServerComponent { context.setReportDirectory(decompressedDirectory); } - public void clean(@Nullable File directory) { + @VisibleForTesting + void saveIssues(ComputeEngineContext context) { + IssueStorage issueStorage = issueStorageFactory.newComputeEngineIssueStorage(context.getProject()); + + File issuesFile = new File(context.getReportDirectory(), "issues.json"); + List issues = new ArrayList<>(MAX_ISSUES_SIZE); + + try { + InputStream issuesStream = new FileInputStream(issuesFile); + JsonReader reader = new JsonReader(new InputStreamReader(issuesStream)); + reader.beginArray(); + while (reader.hasNext()) { + ReportIssue reportIssue = gson.fromJson(reader, ReportIssue.class); + DefaultIssue defaultIssue = toIssue(context, reportIssue); + issues.add(defaultIssue); + if (shouldPersistIssues(issues, reader)) { + issueStorage.save(issues); + issues.clear(); + } + } + + reader.endArray(); + reader.close(); + } catch (IOException e) { + throw new IllegalStateException("Failed to read issues", e); + } + } + + private boolean shouldPersistIssues(List issues, JsonReader reader) throws IOException { + return issues.size() == MAX_ISSUES_SIZE || !reader.hasNext(); + } + + private DefaultIssue toIssue(ComputeEngineContext context, ReportIssue issue) { + ReportComponent component = context.getComponentByBatchId(issue.componentBatchId()); + DefaultIssue defaultIssue = new DefaultIssue(); + defaultIssue.setKey(issue.key()); + defaultIssue.setComponentId(Long.valueOf(component.id())); + defaultIssue.setRuleKey(RuleKey.of(issue.ruleRepo(), issue.ruleKey())); + defaultIssue.setSeverity(issue.severity()); + defaultIssue.setManualSeverity(issue.isManualSeverity()); + defaultIssue.setMessage(issue.message()); + defaultIssue.setLine(issue.line()); + defaultIssue.setEffortToFix(issue.effortToFix()); + setDebt(defaultIssue, issue.debt()); + defaultIssue.setStatus(issue.status()); + defaultIssue.setResolution(issue.resolution()); + defaultIssue.setReporter(issue.reporter()); + defaultIssue.setAssignee(issue.assignee()); + defaultIssue.setChecksum(issue.checksum()); + defaultIssue.setAttributes(KeyValueFormat.parse(issue.issueAttributes())); + defaultIssue.setAuthorLogin(issue.authorLogin()); + defaultIssue.setActionPlanKey(issue.actionPlanKey()); + defaultIssue.setCreationDate(issue.creationDate()); + defaultIssue.setUpdateDate(issue.updateDate()); + defaultIssue.setCloseDate(issue.closeDate()); + defaultIssue.setCurrentChange(FieldDiffs.parse(issue.diffFields())); + defaultIssue.setChanged(issue.isChanged()); + defaultIssue.setNew(issue.isNew()); + defaultIssue.setSelectedAt(issue.selectedAt()); + return defaultIssue; + } + + private DefaultIssue setDebt(DefaultIssue issue, Long debt) { + if (debt != null) { + issue.setDebt(Duration.create(debt)); + } + + return issue; + } + + public void deleteDirectory(@Nullable File directory) { if (directory == null) { return; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputeEngineContext.java b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputeEngineContext.java index d0e473da8e0..c62ccf1dbb5 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputeEngineContext.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputeEngineContext.java @@ -20,18 +20,25 @@ package org.sonar.server.computation; +import com.google.common.annotations.VisibleForTesting; +import org.sonar.batch.protocol.output.resource.ReportComponent; +import org.sonar.batch.protocol.output.resource.ReportComponents; import org.sonar.core.component.ComponentDto; import org.sonar.core.computation.db.AnalysisReportDto; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; import java.io.File; +import java.util.HashMap; +import java.util.Map; public class ComputeEngineContext { private final AnalysisReportDto reportDto; private final ComponentDto project; private File reportDirectory; + private Map components = new HashMap<>(); public ComputeEngineContext(AnalysisReportDto reportDto, ComponentDto project) { this.reportDto = reportDto; @@ -53,4 +60,25 @@ public class ComputeEngineContext { public void setReportDirectory(@Nullable File reportDirectory) { this.reportDirectory = reportDirectory; } + + public void addResources(ReportComponents reportComponents) { + addResource(reportComponents.root()); + } + + @CheckForNull + public ReportComponent getComponentByBatchId(Long batchId) { + return components.get(batchId); + } + + @VisibleForTesting + Map getComponents() { + return components; + } + + private void addResource(ReportComponent resource) { + this.components.put(resource.batchId(), resource); + for (ReportComponent childResource : resource.children()) { + addResource(childResource); + } + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputeEngineIssueStorage.java b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputeEngineIssueStorage.java new file mode 100644 index 00000000000..69ccf54ba8a --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputeEngineIssueStorage.java @@ -0,0 +1,102 @@ +/* + * 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; + +import com.google.common.annotations.VisibleForTesting; +import org.sonar.api.issue.Issue; +import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.RuleFinder; +import org.sonar.core.component.ComponentDto; +import org.sonar.core.issue.db.IssueDto; +import org.sonar.core.issue.db.IssueMapper; +import org.sonar.core.issue.db.IssueStorage; +import org.sonar.core.issue.db.UpdateConflictResolver; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.persistence.MyBatis; +import org.sonar.server.db.DbClient; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +class ComputeEngineIssueStorage extends IssueStorage { + + private final DbClient dbClient; + private final ComponentDto project; + private final UpdateConflictResolver conflictResolver = new UpdateConflictResolver(); + + public ComputeEngineIssueStorage(MyBatis mybatis, DbClient dbClient, RuleFinder ruleFinder, ComponentDto project) { + super(mybatis, ruleFinder); + this.dbClient = dbClient; + this.project = project; + } + + @Override + protected void doInsert(DbSession session, long now, DefaultIssue issue) { + IssueMapper issueMapper = session.getMapper(IssueMapper.class); + long componentId = componentId(session, issue); + long projectId = projectId(); + Rule rule = rule(issue); + List allTags = new ArrayList(); + allTags.addAll(Arrays.asList(rule.getTags())); + allTags.addAll(Arrays.asList(rule.getSystemTags())); + issue.setTags(allTags); + IssueDto dto = IssueDto.toDtoForBatchInsert(issue, componentId, projectId, rule.getId(), now); + issueMapper.insert(dto); + } + + @Override + protected void doUpdate(DbSession session, long now, DefaultIssue issue) { + IssueMapper issueMapper = session.getMapper(IssueMapper.class); + IssueDto dto = IssueDto.toDtoForUpdate(issue, projectId(), now); + if (Issue.STATUS_CLOSED.equals(issue.status()) || issue.selectedAt() == null) { + // Issue is closed by scan or changed by end-user + issueMapper.update(dto); + + } else { + int count = issueMapper.updateIfBeforeSelectedDate(dto); + if (count == 0) { + // End-user and scan changed the issue at the same time. + // See https://jira.codehaus.org/browse/SONAR-4309 + conflictResolver.resolve(issue, issueMapper); + } + } + } + + @VisibleForTesting + long componentId(DbSession session, DefaultIssue issue) { + if (issue.componentId() != null) { + return issue.componentId(); + } + + ComponentDto componentDto = dbClient.componentDao().getNullableByKey(session, issue.componentKey()); + if (componentDto == null) { + throw new IllegalStateException("Unknown component: " + issue.componentKey()); + } + return componentDto.getId(); + } + + @VisibleForTesting + long projectId() { + return project.getId(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputeEngineIssueStorageFactory.java b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputeEngineIssueStorageFactory.java new file mode 100644 index 00000000000..e9326d0b9c0 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputeEngineIssueStorageFactory.java @@ -0,0 +1,44 @@ +/* + * 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; + +import org.sonar.api.ServerComponent; +import org.sonar.api.rules.RuleFinder; +import org.sonar.core.component.ComponentDto; +import org.sonar.core.issue.db.IssueStorage; +import org.sonar.core.persistence.MyBatis; +import org.sonar.server.db.DbClient; + +public class ComputeEngineIssueStorageFactory implements ServerComponent { + private final MyBatis myBatis; + private final DbClient dbClient; + private final RuleFinder ruleFinder; + + public ComputeEngineIssueStorageFactory(MyBatis myBatis, DbClient dbClient, RuleFinder ruleFinder) { + this.myBatis = myBatis; + this.dbClient = dbClient; + this.ruleFinder = ruleFinder; + } + + public IssueStorage newComputeEngineIssueStorage(ComponentDto project) { + return new ComputeEngineIssueStorage(myBatis, dbClient, ruleFinder, project); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/db/AnalysisReportDao.java b/server/sonar-server/src/main/java/org/sonar/server/computation/db/AnalysisReportDao.java index fd2ac2de5a3..c6a7bf5ca6e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/db/AnalysisReportDao.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/db/AnalysisReportDao.java @@ -146,10 +146,8 @@ public class AnalysisReportDao extends BaseDao issues = null; + + protected FakeIssueStorage() { + super(mock(MyBatis.class), mock(RuleFinder.class)); + } + + @Override + public void save(Iterable issues) { + this.issues = Lists.newArrayList(issues); + } + + @Override + protected void doInsert(DbSession batchSession, long now, DefaultIssue issue) { + + } + + @Override + protected void doUpdate(DbSession batchSession, long now, DefaultIssue issue) { + + } + } + + private static class FakeComputeEngineContext extends ComputeEngineContext { + + public FakeComputeEngineContext() { + super(mock(AnalysisReportDto.class), mock(ComponentDto.class)); + } + + @Override + public ReportComponent getComponentByBatchId(Long batchId) { + return new ReportComponent() + .setBatchId(123) + .setId(456); + } + } + } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/ComputeEngineIssueStorageFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/ComputeEngineIssueStorageFactoryTest.java new file mode 100644 index 00000000000..492e71a44dc --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/ComputeEngineIssueStorageFactoryTest.java @@ -0,0 +1,45 @@ +/* + * 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; + +import org.junit.Test; +import org.sonar.api.rules.RuleFinder; +import org.sonar.core.component.ComponentDto; +import org.sonar.core.issue.db.IssueStorage; +import org.sonar.core.persistence.MyBatis; +import org.sonar.server.db.DbClient; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class ComputeEngineIssueStorageFactoryTest { + + ComputeEngineIssueStorageFactory sut; + + @Test + public void return_instance_of_compute_engine_issue_storage() throws Exception { + sut = new ComputeEngineIssueStorageFactory(mock(MyBatis.class), mock(DbClient.class), mock(RuleFinder.class)); + + IssueStorage issueStorage = sut.newComputeEngineIssueStorage(mock(ComponentDto.class)); + + assertThat(issueStorage).isInstanceOf(ComputeEngineIssueStorage.class); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/ComputeEngineIssueStorageTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/ComputeEngineIssueStorageTest.java new file mode 100644 index 00000000000..b63f9ba3e24 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/ComputeEngineIssueStorageTest.java @@ -0,0 +1,251 @@ +/* + * 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; + +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.issue.internal.DefaultIssue; +import org.sonar.api.issue.internal.DefaultIssueComment; +import org.sonar.api.issue.internal.IssueChangeContext; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.RuleFinder; +import org.sonar.api.rules.RuleQuery; +import org.sonar.api.utils.DateUtils; +import org.sonar.api.utils.Duration; +import org.sonar.api.utils.System2; +import org.sonar.batch.protocol.output.resource.ReportComponent; +import org.sonar.core.component.ComponentDto; +import org.sonar.core.persistence.AbstractDaoTestCase; +import org.sonar.core.persistence.DbSession; +import org.sonar.server.component.db.ComponentDao; +import org.sonar.server.db.DbClient; +import org.sonar.server.issue.db.IssueDao; + +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ComputeEngineIssueStorageTest extends AbstractDaoTestCase { + + DbClient dbClient; + DbSession dbSession; + Map components; + ComponentDto project; + + ComputeEngineIssueStorage sut; + + @Before + public void setUp() throws Exception { + System2 system = mock(System2.class); + when(system.now()).thenReturn(2000000000L); + dbClient = new DbClient(getDatabase(), getMyBatis(), + new ComponentDao(system), + new IssueDao(getMyBatis()), + new ComponentDao(system)); + dbSession = dbClient.openSession(false); + components = new HashMap<>(); + project = new ComponentDto(); + + sut = new ComputeEngineIssueStorage(getMyBatis(), dbClient, new FakeRuleFinder(), project); + } + + @Test + public void should_get_component_id_set_in_issue() throws Exception { + DefaultIssue issue = new DefaultIssue().setComponentId(123L); + + long componentId = sut.componentId(dbSession, issue); + + assertThat(componentId).isEqualTo(123L); + } + + @Test + public void should_load_component_id_from_db() throws Exception { + setupData("should_load_component_id_from_db"); + + long componentId = sut.componentId(dbSession, new DefaultIssue().setComponentKey("struts:Action.java")); + + assertThat(componentId).isEqualTo(123); + } + + @Test(expected = IllegalStateException.class) + public void should_fail_to_load_component_id_if_unknown_component() throws Exception { + setupData("should_fail_to_load_component_id_if_unknown_component"); + + sut.componentId(dbSession, new DefaultIssue().setComponentKey("struts:Action.java")); + } + + @Test + public void should_load_project_id() throws Exception { + project.setId(100L); + + long projectId = sut.projectId(); + + assertThat(projectId).isEqualTo(100); + } + + @Test + public void should_insert_new_issues() throws Exception { + setupData("should_insert_new_issues"); + project.setId(10L).setKey("struts"); + + DefaultIssueComment comment = DefaultIssueComment.create("ABCDE", "emmerik", "the comment"); + // override generated key + comment.setKey("FGHIJ"); + + Date date = DateUtils.parseDate("2013-05-18"); + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setNew(true) + .setRuleKey(RuleKey.of("squid", "AvoidCycle")) + .setLine(5000) + .setDebt(Duration.create(10L)) + .setReporter("emmerik") + .setResolution("OPEN") + .setStatus("OPEN") + .setSeverity("BLOCKER") + .setAttribute("foo", "bar") + .addComment(comment) + .setCreationDate(date) + .setUpdateDate(date) + .setCloseDate(date) + + .setComponentKey("struts:Action"); + + sut.save(issue); + + checkTables("should_insert_new_issues", new String[] {"id", "created_at", "updated_at", "issue_change_creation_date"}, "issues", "issue_changes"); + } + + @Test + public void should_update_issues() throws Exception { + setupData("should_update_issues"); + + IssueChangeContext context = IssueChangeContext.createUser(new Date(), "emmerik"); + + project.setId(10L).setKey("struts"); + + DefaultIssueComment comment = DefaultIssueComment.create("ABCDE", "emmerik", "the comment"); + // override generated key + comment.setKey("FGHIJ"); + + Date date = DateUtils.parseDate("2013-05-18"); + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setNew(false) + .setChanged(true) + + // updated fields + .setLine(5000) + .setDebt(Duration.create(10L)) + .setChecksum("FFFFF") + .setAuthorLogin("simon") + .setAssignee("loic") + .setFieldChange(context, "severity", "INFO", "BLOCKER") + .setReporter("emmerik") + .setResolution("FIXED") + .setStatus("RESOLVED") + .setSeverity("BLOCKER") + .setAttribute("foo", "bar") + .addComment(comment) + .setCreationDate(date) + .setUpdateDate(date) + .setCloseDate(date) + + // unmodifiable fields + .setRuleKey(RuleKey.of("xxx", "unknown")) + .setComponentKey("not:a:component"); + + sut.save(issue); + + checkTables("should_update_issues", new String[] {"id", "created_at", "updated_at", "issue_change_creation_date"}, "issues", "issue_changes"); + } + + @Test + public void should_resolve_conflicts_on_updates() throws Exception { + setupData("should_resolve_conflicts_on_updates"); + + project.setId(10L).setKey("struts"); + + Date date = DateUtils.parseDate("2013-05-18"); + DefaultIssue issue = new DefaultIssue() + .setKey("ABCDE") + .setNew(false) + .setChanged(true) + .setCreationDate(DateUtils.parseDate("2005-05-12")) + .setUpdateDate(date) + .setRuleKey(RuleKey.of("squid", "AvoidCycles")) + .setComponentKey("struts:Action") + + // issue in database has been updated in 2015, after the loading by scan + .setSelectedAt(1400000000000L) + + // fields to be updated + .setLine(444) + .setSeverity("BLOCKER") + .setChecksum("FFFFF") + .setAttribute("JIRA", "http://jira.com") + + // fields overridden by end-user -> do not save + .setAssignee("looser") + .setResolution(null) + .setStatus("REOPEN"); + + sut.save(issue); + + checkTables("should_resolve_conflicts_on_updates", new String[] {"id", "created_at", "updated_at", "issue_change_creation_date"}, "issues"); + } + + static class FakeRuleFinder implements RuleFinder { + + @Override + public Rule findById(int ruleId) { + return null; + } + + @Override + public Rule findByKey(String repositoryKey, String key) { + return null; + } + + @Override + public Rule findByKey(RuleKey key) { + Rule rule = Rule.create().setRepositoryKey(key.repository()).setKey(key.rule()); + rule.setId(200); + return rule; + } + + @Override + public Rule find(RuleQuery query) { + return null; + } + + @Override + public Collection findAll(RuleQuery query) { + return null; + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/DataCleanerStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/DataCleanerStepTest.java index b350790bed2..1d465a85c7d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/DataCleanerStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/DataCleanerStepTest.java @@ -57,6 +57,6 @@ public class DataCleanerStepTest { sut.execute(mock(DbSession.class), context); verify(projectCleaner).purge(any(DbSession.class), any(IdUuidPair.class)); - // verify(reportService).clean(any(File.class)); + // verify(reportService).deleteDirectory(any(File.class)); } } diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/fake-report-folder/empty.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/fake-report-folder/empty.xml deleted file mode 100644 index 871dedcb5e9..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/fake-report-folder/empty.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/fake-report-folder/snapshots.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/fake-report-folder/snapshots.xml deleted file mode 100644 index 642ac79c026..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/fake-report-folder/snapshots.xml +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/fake-report-folder/sub-folder/zip.zip b/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/fake-report-folder/sub-folder/zip.zip deleted file mode 100644 index a540bc5b5ca..00000000000 Binary files a/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/fake-report-folder/sub-folder/zip.zip and /dev/null differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/report-folder/components.json b/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/report-folder/components.json new file mode 100644 index 00000000000..4c642a06ba0 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/report-folder/components.json @@ -0,0 +1,41 @@ +{ + "analysisDate": "2012-12-12T00:00:00+0100", + "root": { + "batchId": 1, + "id": 11, + "snapshotId": 111, + "name": "Root project", + "type": "PRJ", + "children": [ + { + "batchId": 2, + "id": 22, + "snapshotId": 222, + "path": "module1", + "name": "Module", + "type": "MOD", + "children": [ + { + "batchId": 3, + "id": 33, + "snapshotId": 333, + "path": "src", + "name": "src", + "type": "DIR", + "children": [ + { + "batchId": 4, + "id": 44, + "snapshotId": 444, + "path": "Foo.java", + "name": "Foo.java", + "type": "FIL", + "children": [] + } + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/report-folder/empty.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/report-folder/empty.xml new file mode 100644 index 00000000000..871dedcb5e9 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/report-folder/empty.xml @@ -0,0 +1,3 @@ + + + diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/report-folder/issues.json b/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/report-folder/issues.json new file mode 100644 index 00000000000..fa3eec4d9f7 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/report-folder/issues.json @@ -0,0 +1,182 @@ +[ + { + "isNew": false, + "key": "key", + "manualSeverity": false, + "assignee": "Assignee", + "attributes": "attributes", + "authorLogin": "login", + "isChanged": true, + "ruleKey": "ruleKey", + "ruleRepo": "local", + "severity": "INFO", + "message": "message", + "line": 25, + "effortToFix": 30, + "debtInMinutes": 25, + "status": "NEW", + "resolution": "Non Solved", + "reporter": "reporter", + "assignee": "assignee", + "checkSum": "checkSum", + "attributes": "toto=25", + "authorLogin": "author", + "actionPlanKey": "actionPlanKey", + "creationDate": "2014-12-19T00:03:14+0100", + "updateDate": "2014-12-19T00:03:14+0100", + "closeDate": "2014-12-19T00:03:14+0100", + "currentChange": "coucou,c'est,nous", + "isChange": false, + "selectAt": 564897564 + }, + { + "isNew": false, + "key": "key", + "manualSeverity": false, + "assignee": "Assignee", + "attributes": "attributes", + "authorLogin": "login", + "isChanged": true, + "ruleKey": "ruleKey", + "ruleRepo": "local", + "severity": "INFO", + "message": "message", + "line": 25, + "effortToFix": 30, + "debtInMinutes": 25, + "status": "NEW", + "resolution": "Non Solved", + "reporter": "reporter", + "assignee": "assignee", + "checkSum": "checkSum", + "attributes": "toto=25", + "authorLogin": "author", + "actionPlanKey": "actionPlanKey", + "creationDate": "2014-12-19T00:03:14+0100", + "updateDate": "2014-12-19T00:03:14+0100", + "closeDate": "2014-12-19T00:03:14+0100", + "currentChange": "coucou,c'est,nous", + "isChange": false, + "selectAt": 564897564 + }, + { + "isNew": false, + "key": "key", + "manualSeverity": false, + "assignee": "Assignee", + "attributes": "attributes", + "authorLogin": "login", + "isChanged": true, + "ruleKey": "ruleKey", + "ruleRepo": "local", + "severity": "INFO", + "message": "message", + "line": 25, + "effortToFix": 30, + "debtInMinutes": 25, + "status": "NEW", + "resolution": "Non Solved", + "reporter": "reporter", + "assignee": "assignee", + "checkSum": "checkSum", + "attributes": "toto=25", + "authorLogin": "author", + "actionPlanKey": "actionPlanKey", + "creationDate": "2014-12-19T00:03:14+0100", + "updateDate": "2014-12-19T00:03:14+0100", + "closeDate": "2014-12-19T00:03:14+0100", + "currentChange": "coucou,c'est,nous", + "isChange": false, + "selectAt": 564897564 + }, + { + "isNew": false, + "key": "key", + "manualSeverity": false, + "assignee": "Assignee", + "attributes": "attributes", + "authorLogin": "login", + "isChanged": true, + "ruleKey": "ruleKey", + "ruleRepo": "local", + "severity": "INFO", + "message": "message", + "line": 25, + "effortToFix": 30, + "debtInMinutes": 25, + "status": "NEW", + "resolution": "Non Solved", + "reporter": "reporter", + "assignee": "assignee", + "checkSum": "checkSum", + "attributes": "toto=25", + "authorLogin": "author", + "actionPlanKey": "actionPlanKey", + "creationDate": "2014-12-19T00:03:14+0100", + "updateDate": "2014-12-19T00:03:14+0100", + "closeDate": "2014-12-19T00:03:14+0100", + "currentChange": "coucou,c'est,nous", + "isChange": false, + "selectAt": 564897564 + }, + { + "isNew": false, + "key": "key", + "manualSeverity": false, + "assignee": "Assignee", + "attributes": "attributes", + "authorLogin": "login", + "isChanged": true, + "ruleKey": "ruleKey", + "ruleRepo": "local", + "severity": "INFO", + "message": "message", + "line": 25, + "effortToFix": 30, + "debtInMinutes": 25, + "status": "NEW", + "resolution": "Non Solved", + "reporter": "reporter", + "assignee": "assignee", + "checkSum": "checkSum", + "attributes": "toto=25", + "authorLogin": "author", + "actionPlanKey": "actionPlanKey", + "creationDate": "2014-12-19T00:03:14+0100", + "updateDate": "2014-12-19T00:03:14+0100", + "closeDate": "2014-12-19T00:03:14+0100", + "currentChange": "coucou,c'est,nous", + "isChange": false, + "selectAt": 564897564 + }, + { + "isNew": false, + "key": "key", + "manualSeverity": false, + "assignee": "Assignee", + "attributes": "attributes", + "authorLogin": "login", + "isChanged": true, + "ruleKey": "ruleKey", + "ruleRepo": "local", + "severity": "INFO", + "message": "message", + "line": 25, + "effortToFix": 30, + "debtInMinutes": 25, + "status": "NEW", + "resolution": "Non Solved", + "reporter": "reporter", + "assignee": "assignee", + "checkSum": "checkSum", + "attributes": "toto=25", + "authorLogin": "author", + "actionPlanKey": "actionPlanKey", + "creationDate": "2014-12-19T00:03:14+0100", + "updateDate": "2014-12-19T00:03:14+0100", + "closeDate": "2014-12-19T00:03:14+0100", + "currentChange": "coucou,c'est,nous", + "isChange": false, + "selectAt": 564897564 + } +] \ No newline at end of file diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/report-folder/snapshots.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/report-folder/snapshots.xml new file mode 100644 index 00000000000..642ac79c026 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/report-folder/snapshots.xml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/report-folder/sub-folder/zip.zip b/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/report-folder/sub-folder/zip.zip new file mode 100644 index 00000000000..a540bc5b5ca Binary files /dev/null and b/server/sonar-server/src/test/resources/org/sonar/server/computation/AnalysisReportServiceTest/report-folder/sub-folder/zip.zip differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_fail_to_load_component_id_if_unknown_component.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_fail_to_load_component_id_if_unknown_component.xml new file mode 100644 index 00000000000..5ed00ba028b --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_fail_to_load_component_id_if_unknown_component.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_insert_new_issues-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_insert_new_issues-result.xml new file mode 100644 index 00000000000..458ce2553c2 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_insert_new_issues-result.xml @@ -0,0 +1,27 @@ + + + + + + diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_insert_new_issues.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_insert_new_issues.xml new file mode 100644 index 00000000000..02899f1459d --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_insert_new_issues.xml @@ -0,0 +1,4 @@ + + + + diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_load_component_id_from_db.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_load_component_id_from_db.xml new file mode 100644 index 00000000000..99098d271cc --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_load_component_id_from_db.xml @@ -0,0 +1,3 @@ + + + diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_resolve_conflicts_on_updates-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_resolve_conflicts_on_updates-result.xml new file mode 100644 index 00000000000..e421b224b5e --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_resolve_conflicts_on_updates-result.xml @@ -0,0 +1,34 @@ + + + + + + + + diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_resolve_conflicts_on_updates.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_resolve_conflicts_on_updates.xml new file mode 100644 index 00000000000..0ddfc4586ab --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_resolve_conflicts_on_updates.xml @@ -0,0 +1,34 @@ + + + + + + + + + diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_update_issues-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_update_issues-result.xml new file mode 100644 index 00000000000..ba65e9df145 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_update_issues-result.xml @@ -0,0 +1,33 @@ + + + + + + diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_update_issues.xml b/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_update_issues.xml new file mode 100644 index 00000000000..5fd4a9e813c --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/ComputeEngineIssueStorageTest/should_update_issues.xml @@ -0,0 +1,31 @@ + + + + + + + diff --git a/sonar-batch-protocol/src/test/java/org/sonar/batch/protocol/input/ProjectReferentialsTest.java b/sonar-batch-protocol/src/test/java/org/sonar/batch/protocol/input/ProjectReferentialsTest.java index 316d59acc31..96af4da66fc 100644 --- a/sonar-batch-protocol/src/test/java/org/sonar/batch/protocol/input/ProjectReferentialsTest.java +++ b/sonar-batch-protocol/src/test/java/org/sonar/batch/protocol/input/ProjectReferentialsTest.java @@ -53,7 +53,6 @@ public class ProjectReferentialsTest { ref.addFileData("foo", "src/main/java/Foo.java", new FileData("xyz", "1=12345,2=3456", "1=345,2=345", "1=henryju,2=gaudin")); ref.addFileData("foo", "src/main/java/Foo2.java", new FileData("xyz", "1=12345,2=3456", "1=345,2=345", "1=henryju,2=gaudin")); - System.out.println(ref.toJson()); JSONAssert .assertEquals( "{timestamp:10," diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/IssuePersister.java b/sonar-batch/src/main/java/org/sonar/batch/issue/IssuePersister.java index e1ca3f3b1c5..1abf3ca1f23 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/IssuePersister.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/IssuePersister.java @@ -49,6 +49,6 @@ public class IssuePersister implements ScanPersister { return; } Iterable issues = issueCache.all(); - storage.save(issues); + // storage.save(issues); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/IssuePersisterTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/IssuePersisterTest.java index eb0950ac88d..a48e54a9312 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/issue/IssuePersisterTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/issue/IssuePersisterTest.java @@ -28,11 +28,7 @@ import org.sonar.core.persistence.AbstractDaoTestCase; import java.util.Arrays; import java.util.List; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; public class IssuePersisterTest extends AbstractDaoTestCase { @@ -53,10 +49,10 @@ public class IssuePersisterTest extends AbstractDaoTestCase { } @Test - public void should_persist_all_issues() throws Exception { + public void should_not_persist_issues_anymore() throws Exception { persister.persist(); - verify(storage, times(1)).save(issues); + verify(storage, never()).save(issues); } @Test diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java index 53a8cd5064a..7157be3646a 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java @@ -571,8 +571,8 @@ public final class IssueDto implements Serializable { .setResolution(issue.resolution()) .setStatus(issue.status()) .setSeverity(issue.severity()) - .setChecksum(issue.checksum()) .setManualSeverity(issue.manualSeverity()) + .setChecksum(issue.checksum()) .setReporter(issue.reporter()) .setAssignee(issue.assignee()) .setRuleId(ruleId) @@ -595,7 +595,7 @@ public final class IssueDto implements Serializable { .setIssueUpdateDate(issue.updateDate()) .setSelectedAt(issue.selectedAt()) - // technical dates + // technical dates .setCreatedAt(now) .setUpdatedAt(now); } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueStorage.java b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueStorage.java index bfc72487c1d..c4533c9bf0d 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueStorage.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueStorage.java @@ -74,7 +74,7 @@ public abstract class IssueStorage { // Batch session can not be used for updates. It does not return the number of updated rows, // required for detecting conflicts. long now = System.currentTimeMillis(); - List toBeUpdated = batchInsert(session, issues, now); + List toBeUpdated = batchInsertAndReturnIssuesToUpdate(session, issues, now); update(toBeUpdated, now); doAfterSave(); } @@ -83,7 +83,7 @@ public abstract class IssueStorage { // overridden on server-side to index ES } - private List batchInsert(DbSession session, Iterable issues, long now) { + private List batchInsertAndReturnIssuesToUpdate(DbSession session, Iterable issues, long now) { List toBeUpdated = newArrayList(); int count = 0; IssueChangeMapper issueChangeMapper = session.getMapper(IssueChangeMapper.class); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/internal/DefaultIssue.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/internal/DefaultIssue.java index e318234f0e0..8b1e92e7df1 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/issue/internal/DefaultIssue.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/internal/DefaultIssue.java @@ -40,14 +40,7 @@ import javax.annotation.CheckForNull; import javax.annotation.Nullable; import java.io.Serializable; -import java.util.Calendar; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import static com.google.common.collect.Lists.newArrayList; @@ -514,6 +507,12 @@ public class DefaultIssue implements Issue { return this; } + public DefaultIssue setCurrentChange(FieldDiffs currentChange) { + this.currentChange = currentChange; + addChange(currentChange); + return this; + } + @CheckForNull public FieldDiffs currentChange() { return currentChange;