diff options
6 files changed, 123 insertions, 53 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/dbcleaner/ProjectCleaner.java b/server/sonar-server/src/main/java/org/sonar/server/computation/dbcleaner/ProjectCleaner.java index d2969f7c840..1def57fd52b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/dbcleaner/ProjectCleaner.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/dbcleaner/ProjectCleaner.java @@ -60,8 +60,8 @@ public class ProjectCleaner { PurgeConfiguration configuration = newDefaultPurgeConfiguration(projectConfig, idUuidPair, disabledComponentUuids); - cleanHistoricalData(session, configuration.rootProjectIdUuid().getUuid(), projectConfig); - doPurge(session, configuration); + periodCleaner.clean(session, configuration.rootProjectIdUuid().getUuid(), projectConfig); + purgeDao.purge(session, configuration, purgeListener, profiler); session.commit(); logProfiling(start, projectConfig); @@ -76,22 +76,4 @@ public class ProjectCleaner { LOG.info("\n -------- End of profiling for purge --------\n"); } } - - private void cleanHistoricalData(DbSession session, String rootUuid, Configuration config) { - try { - periodCleaner.clean(session, rootUuid, config); - } catch (Exception e) { - // purge errors must no fail the batch - LOG.error("Fail to clean historical data [uuid=" + rootUuid + "]", e); - } - } - - private void doPurge(DbSession session, PurgeConfiguration configuration) { - try { - purgeDao.purge(session, configuration, purgeListener, profiler); - } catch (Exception e) { - // purge errors must no fail the report analysis - LOG.error("Fail to purge data [id=" + configuration.rootProjectIdUuid().getId() + "]", e); - } - } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/dbcleaner/ProjectCleanerTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/dbcleaner/ProjectCleanerTest.java index 611a8d14c53..fc9c4ab88fb 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/dbcleaner/ProjectCleanerTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/dbcleaner/ProjectCleanerTest.java @@ -86,22 +86,4 @@ public class ProjectCleanerTest { verify(periodCleaner).clean(any(DbSession.class), anyString(), any(Configuration.class)); verify(dao).purge(any(DbSession.class), any(PurgeConfiguration.class), any(PurgeListener.class), any(PurgeProfiler.class)); } - - @Test - public void if_dao_purge_fails_it_should_not_interrupt_program_execution() { - doThrow(RuntimeException.class).when(dao).purge(any(DbSession.class), any(PurgeConfiguration.class), any(PurgeListener.class), any(PurgeProfiler.class)); - - underTest.purge(mock(DbSession.class), mock(IdUuidPair.class), settings.asConfig(), emptyList()); - - verify(dao).purge(any(DbSession.class), any(PurgeConfiguration.class), any(PurgeListener.class), any(PurgeProfiler.class)); - } - - @Test - public void if_profiler_cleaning_fails_it_should_not_interrupt_program_execution() { - doThrow(RuntimeException.class).when(periodCleaner).clean(any(DbSession.class), anyString(), any(Configuration.class)); - - underTest.purge(mock(DbSession.class), mock(IdUuidPair.class), settings.asConfig(), emptyList()); - - verify(periodCleaner).clean(any(DbSession.class), anyString(), any(Configuration.class)); - } } diff --git a/tests/projects/analysis/resilience/resilience-purge/sonar-project.properties b/tests/projects/analysis/resilience/resilience-purge/sonar-project.properties new file mode 100644 index 00000000000..9e4aa0e3584 --- /dev/null +++ b/tests/projects/analysis/resilience/resilience-purge/sonar-project.properties @@ -0,0 +1,5 @@ +sonar.projectKey=sample +sonar.projectName=Sample +sonar.projectVersion=1.0-SNAPSHOT +sonar.sources=src/main/xoo +sonar.language=xoo diff --git a/tests/projects/analysis/resilience/resilience-purge/src/main/xoo/sample/Sample.xoo b/tests/projects/analysis/resilience/resilience-purge/src/main/xoo/sample/Sample.xoo new file mode 100644 index 00000000000..41871e123a3 --- /dev/null +++ b/tests/projects/analysis/resilience/resilience-purge/src/main/xoo/sample/Sample.xoo @@ -0,0 +1,16 @@ +package sample; + +public class Sample { + + public Sample(int i) { + int j = i++; + } + + private String myMethod() { + if (foo == bar) { + return "hello"; + } else { + throw new IllegalStateException(); + } + } +} diff --git a/tests/projects/analysis/resilience/resilience-purge/src/main/xoo/sample/Sample.xoo.measures b/tests/projects/analysis/resilience/resilience-purge/src/main/xoo/sample/Sample.xoo.measures new file mode 100644 index 00000000000..06c9b6c2f38 --- /dev/null +++ b/tests/projects/analysis/resilience/resilience-purge/src/main/xoo/sample/Sample.xoo.measures @@ -0,0 +1,9 @@ +ncloc:13 +#Used by dashboard/widgets tests +complexity:3 +complexity_in_classes:3 +cognitive_complexity:4 +classes:1 +comment_lines:3 +public_api:5 +public_undocumented_api:2 diff --git a/tests/src/test/java/org/sonarqube/tests/analysis/AnalysisEsResilienceTest.java b/tests/src/test/java/org/sonarqube/tests/analysis/AnalysisEsResilienceTest.java index b522d311f74..7476b9842c4 100644 --- a/tests/src/test/java/org/sonarqube/tests/analysis/AnalysisEsResilienceTest.java +++ b/tests/src/test/java/org/sonarqube/tests/analysis/AnalysisEsResilienceTest.java @@ -30,12 +30,14 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.annotation.Nullable; import org.junit.After; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.sonarqube.tests.Byteman; import org.sonarqube.tests.Tester; +import org.sonarqube.ws.Common; import org.sonarqube.ws.Issues; import org.sonarqube.ws.Organizations.Organization; import org.sonarqube.ws.QualityProfiles.CreateWsResponse.QualityProfile; @@ -94,32 +96,32 @@ public class AnalysisEsResilienceTest { .activateRule(profile, "xoo:OneIssuePerFile") .assignQProfileToProject(profile, project); - executeAnalysis(projectKey, organization, orgAdministrator, "analysis/resilience/resilience-sample-v1"); + executeAnalysis(projectKey, organization, orgAdministrator, "analysis/resilience/resilience-sample-v1", null); assertThat(searchFile(fileKey, organization)).isNotEmpty(); assertThat(searchFile(file2Key, organization)).isEmpty(); assertThat(searchFile(file3Key, organization)).isEmpty(); - List<Issues.Issue> issues = searchIssues(projectKey); - assertThat(issues) + Issues.SearchWsResponse issues = searchIssues(projectKey); + assertThat(issues.getIssuesList()) .extracting(Issues.Issue::getComponent) .containsExactlyInAnyOrder(fileKey); byteman.activateScript("resilience/making_ce_indexation_failing.btm"); - executeAnalysis(projectKey, organization, orgAdministrator, "analysis/resilience/resilience-sample-v2"); + executeAnalysis(projectKey, organization, orgAdministrator, "analysis/resilience/resilience-sample-v2", null); assertThat(searchFile(fileKey, organization)).isNotEmpty(); assertThat(searchFile(file2Key, organization)).isEmpty();// inconsistency: in DB there is also file2Key assertThat(searchFile(file3Key, organization)).isEmpty();// inconsistency: in DB there is also file3Key issues = searchIssues(projectKey); - assertThat(issues) + assertThat(issues.getIssuesList()) .extracting(Issues.Issue::getComponent) .containsExactlyInAnyOrder(fileKey /* inconsistency: in DB there is also file2Key and file3Key */); byteman.deactivateAllRules(); - executeAnalysis(projectKey, organization, orgAdministrator, "analysis/resilience/resilience-sample-v3"); + executeAnalysis(projectKey, organization, orgAdministrator, "analysis/resilience/resilience-sample-v3", null); assertThat(searchFile(fileKey, organization)).isNotEmpty(); assertThat(searchFile(file2Key, organization)).isEmpty(); assertThat(searchFile(file3Key, organization)).isNotEmpty(); issues = searchIssues(projectKey); - assertThat(issues) + assertThat(issues.getIssuesList()) .extracting(Issues.Issue::getComponent, Issues.Issue::getStatus) .containsExactlyInAnyOrder( tuple(fileKey, "OPEN"), @@ -128,6 +130,76 @@ public class AnalysisEsResilienceTest { } @Test + public void purge_mechanism_must_be_resilient_at_next_analysis() throws Exception { + Organization organization = tester.organizations().generate(); + User orgAdministrator = tester.users().generateAdministrator(organization); + WsProjects.CreateWsResponse.Project project = tester.projects().generate(organization); + String projectKey = project.getKey(); + String fileKey = projectKey + ":src/main/xoo/sample/Sample.xoo"; + + QualityProfile profile = tester.qProfiles().createXooProfile(organization); + tester.qProfiles() + .activateRule(profile, "xoo:OneIssuePerFile") + .assignQProfileToProject(profile, project); + + executeAnalysis(projectKey, organization, orgAdministrator, "analysis/resilience/resilience-purge", "2000-01-01"); + assertThat(searchFile(fileKey, organization)).isNotEmpty(); + Issues.SearchWsResponse issues = searchIssues(projectKey); + assertThat(issues.getIssuesList()) + .extracting(Issues.Issue::getComponent) + .containsExactlyInAnyOrder(fileKey); + + tester.qProfiles() + .deactivateRule(profile, "xoo:OneIssuePerFile"); + + // We are expecting the purge to fail during this analysis + tester.elasticsearch().lockWrites("issues"); + String taskUuid = executeAnalysis(projectKey, organization, orgAdministrator, "analysis/resilience/resilience-purge", "2000-01-02"); + + // The task has failed + WsCe.Task task = tester.wsClient().ce().task(taskUuid).getTask(); + assertThat(task.getStatus()).isEqualTo(WsCe.TaskStatus.FAILED); + + // The issue must be present with status CLOSED in database + assertThat(searchFile(fileKey, organization)).isNotEmpty(); + issues = searchIssues(projectKey); + assertThat(issues.getIssuesList()) + .extracting(Issues.Issue::getComponent, Issues.Issue::getStatus) + .containsExactlyInAnyOrder( + tuple(fileKey, "CLOSED")); + + // Now we have an inconstency : ES is seeing the issue as opened + assertThat(issues.getFacets().getFacets(0).getValuesList()) + .extracting(Common.FacetValue::getVal, Common.FacetValue::getCount) + .containsExactlyInAnyOrder( + tuple("OPEN", 1L), + tuple("CONFIRMED", 0L), + tuple("REOPENED", 0L), + tuple("RESOLVED", 0L), + tuple("CLOSED", 0L) + ); + + tester.elasticsearch().unlockWrites("issues"); + + // Second analysis must fix the issue, + // The purge will delete the "old" issue + executeAnalysis(projectKey, organization, orgAdministrator, "analysis/resilience/resilience-purge", null); + assertThat(searchFile(fileKey, organization)).isNotEmpty(); + issues = searchIssues(projectKey); + assertThat(issues.getIssuesList()) + .isEmpty(); + assertThat(issues.getFacets().getFacets(0).getValuesList()) + .extracting(Common.FacetValue::getVal, Common.FacetValue::getCount) + .containsExactlyInAnyOrder( + tuple("OPEN", 0L), + tuple("CONFIRMED", 0L), + tuple("REOPENED", 0L), + tuple("RESOLVED", 0L), + tuple("CLOSED", 0L) + ); + } + + @Test public void compute_engine_task_must_be_red_when_es_is_not_available() throws Exception { Organization organization = tester.organizations().generate(); User orgAdministrator = tester.users().generateAdministrator(organization); @@ -142,17 +214,17 @@ public class AnalysisEsResilienceTest { tester.elasticsearch().lockWrites("issues"); - String analysisKey = executeAnalysis(projectKey, organization, orgAdministrator, "analysis/resilience/resilience-sample-v1"); + String analysisKey = executeAnalysis(projectKey, organization, orgAdministrator, "analysis/resilience/resilience-sample-v1", null); WsCe.TaskResponse task = tester.wsClient().ce().task(analysisKey); assertThat(task.getTask().getStatus()).isEqualTo(FAILED); } - private List<Issues.Issue> searchIssues(String projectKey) { + private Issues.SearchWsResponse searchIssues(String projectKey) { SearchWsRequest request = new SearchWsRequest() - .setProjectKeys(Collections.singletonList(projectKey)); - Issues.SearchWsResponse results = tester.wsClient().issues().search(request); - return results.getIssuesList(); + .setProjectKeys(Collections.singletonList(projectKey)) + .setFacets(Collections.singletonList("statuses")); + return tester.wsClient().issues().search(request); } private List<String> searchFile(String key, Organization organization) { @@ -169,12 +241,16 @@ public class AnalysisEsResilienceTest { return x.collect(Collectors.toList()); } - private String executeAnalysis(String projectKey, Organization organization, User orgAdministrator, String projectPath) { - BuildResult buildResult = orchestrator.executeBuild(SonarScanner.create(projectDir(projectPath), + private String executeAnalysis(String projectKey, Organization organization, User orgAdministrator, String projectPath, @Nullable String date) { + SonarScanner sonarScanner = SonarScanner.create(projectDir(projectPath), "sonar.organization", organization.getKey(), "sonar.projectKey", projectKey, "sonar.login", orgAdministrator.getLogin(), - "sonar.password", orgAdministrator.getLogin())); + "sonar.password", orgAdministrator.getLogin()); + if (date != null) { + sonarScanner.setProperty("sonar.projectDate", date); + } + BuildResult buildResult = orchestrator.executeBuild(sonarScanner); return ItUtils.extractCeTaskId(buildResult); } } |