diff --git a/chutney/server/src/main/java/com/chutneytesting/campaign/domain/CampaignExecutionRepository.java b/chutney/server/src/main/java/com/chutneytesting/campaign/domain/CampaignExecutionRepository.java index c10723a00..54331457d 100644 --- a/chutney/server/src/main/java/com/chutneytesting/campaign/domain/CampaignExecutionRepository.java +++ b/chutney/server/src/main/java/com/chutneytesting/campaign/domain/CampaignExecutionRepository.java @@ -18,17 +18,14 @@ import com.chutneytesting.server.core.domain.scenario.campaign.CampaignExecution; import java.util.List; -import java.util.Optional; import java.util.Set; public interface CampaignExecutionRepository { - Optional currentExecution(Long campaignId); - - List currentExecutions(); + List currentExecutions(Long campaignId); void startExecution(Long campaignId, CampaignExecution campaignExecution); - void stopExecution(Long campaignId); + void stopExecution(Long campaignId, String environment); CampaignExecution getLastExecution(Long campaignId); diff --git a/chutney/server/src/main/java/com/chutneytesting/campaign/infra/CampaignExecutionDBRepository.java b/chutney/server/src/main/java/com/chutneytesting/campaign/infra/CampaignExecutionDBRepository.java index 21845c90e..753f40cba 100644 --- a/chutney/server/src/main/java/com/chutneytesting/campaign/infra/CampaignExecutionDBRepository.java +++ b/chutney/server/src/main/java/com/chutneytesting/campaign/infra/CampaignExecutionDBRepository.java @@ -16,6 +16,7 @@ package com.chutneytesting.campaign.infra; +import static java.util.Collections.emptyList; import static org.apache.commons.lang3.Validate.notBlank; import static org.apache.commons.lang3.Validate.notNull; @@ -33,7 +34,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -50,7 +50,7 @@ public class CampaignExecutionDBRepository implements CampaignExecutionRepositor private final CampaignJpaRepository campaignJpaRepository; private final DatabaseExecutionJpaRepository scenarioExecutionJpaRepository; private final TestCaseRepository testCaseRepository; - private final Map currentCampaignExecutions = new ConcurrentHashMap<>(); + private final Map> currentCampaignExecutions = new ConcurrentHashMap<>(); public CampaignExecutionDBRepository( CampaignExecutionJpaRepository campaignExecutionJpaRepository, @@ -64,24 +64,27 @@ public CampaignExecutionDBRepository( } @Override - public Optional currentExecution(Long campaignId) { - return Optional.ofNullable(campaignId) - .map(id -> currentCampaignExecutions.get(campaignId)); - } - - @Override - public List currentExecutions() { - return new ArrayList<>(currentCampaignExecutions.values()); + public List currentExecutions(Long campaignId) { + return currentCampaignExecutions.getOrDefault(campaignId, emptyList()); } @Override public void startExecution(Long campaignId, CampaignExecution campaignExecution) { - currentCampaignExecutions.put(campaignId, campaignExecution); + List campaignExecutions = new ArrayList<>(); + if (currentCampaignExecutions.containsKey(campaignId)) { + campaignExecutions = currentCampaignExecutions.get(campaignId); + } + campaignExecutions.add(campaignExecution); + currentCampaignExecutions.put(campaignId, campaignExecutions); } @Override - public void stopExecution(Long campaignId) { - currentCampaignExecutions.remove(campaignId); + public void stopExecution(Long campaignId, String environment) { + currentCampaignExecutions.get(campaignId) + .removeIf(exec -> exec.executionEnvironment.equals(environment)); + if (currentCampaignExecutions.get(campaignId).isEmpty()) { + currentCampaignExecutions.remove(campaignId); + } } @Override @@ -176,8 +179,8 @@ private String title(String scenarioId) { } private boolean isCampaignExecutionRunning(CampaignExecutionEntity campaignExecutionEntity) { - return currentExecution(campaignExecutionEntity.campaignId()) - .map(cer -> cer.executionId.equals(campaignExecutionEntity.id())) - .orElse(false); + return currentExecutions(campaignExecutionEntity.campaignId()) + .stream() + .anyMatch(exec -> exec.executionId.equals(campaignExecutionEntity.id())); } } diff --git a/chutney/server/src/main/java/com/chutneytesting/campaign/infra/jpa/CampaignExecutionEntity.java b/chutney/server/src/main/java/com/chutneytesting/campaign/infra/jpa/CampaignExecutionEntity.java index 98c269167..1d7786e95 100644 --- a/chutney/server/src/main/java/com/chutneytesting/campaign/infra/jpa/CampaignExecutionEntity.java +++ b/chutney/server/src/main/java/com/chutneytesting/campaign/infra/jpa/CampaignExecutionEntity.java @@ -101,6 +101,8 @@ public List scenarioExecutions() { return scenarioExecutions; } + public String environment() { return environment;} + public void updateFromDomain(CampaignExecution report, Iterable scenarioExecutions) { //id = report.executionId; //campaignId = report.campaignId; diff --git a/chutney/server/src/main/java/com/chutneytesting/execution/domain/campaign/CampaignAlreadyRunningException.java b/chutney/server/src/main/java/com/chutneytesting/execution/domain/campaign/CampaignAlreadyRunningException.java index 8748270b4..93c89e29b 100644 --- a/chutney/server/src/main/java/com/chutneytesting/execution/domain/campaign/CampaignAlreadyRunningException.java +++ b/chutney/server/src/main/java/com/chutneytesting/execution/domain/campaign/CampaignAlreadyRunningException.java @@ -25,6 +25,9 @@ public class CampaignAlreadyRunningException extends RuntimeException { private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("YYYYMMdd HH:mm:ss"); public CampaignAlreadyRunningException(CampaignExecution currentReport) { - super("Campaign [" + currentReport.campaignName + "] is already running since " + currentReport.startDate.format(DATE_TIME_FORMATTER)); + super(String.format("Campaign [%s] is already running on [%s] since [%s]", + currentReport.campaignName, + currentReport.executionEnvironment, + currentReport.startDate.format(DATE_TIME_FORMATTER))); } } diff --git a/chutney/server/src/main/java/com/chutneytesting/execution/domain/campaign/CampaignExecutionEngine.java b/chutney/server/src/main/java/com/chutneytesting/execution/domain/campaign/CampaignExecutionEngine.java index fb80739b6..8da86ecf2 100644 --- a/chutney/server/src/main/java/com/chutneytesting/execution/domain/campaign/CampaignExecutionEngine.java +++ b/chutney/server/src/main/java/com/chutneytesting/execution/domain/campaign/CampaignExecutionEngine.java @@ -134,13 +134,13 @@ public CampaignExecution executeById(Long campaignId, String environment, String .orElseThrow(() -> new CampaignNotFoundException(campaignId)); } - public Optional currentExecution(Long campaignId) { - return campaignExecutionRepository.currentExecution(campaignId); + public Optional currentExecution(Long campaignId, String environment) { + return campaignExecutionRepository.currentExecutions(campaignId) + .stream() + .filter(exec -> exec.executionEnvironment.equals(environment)) + .findAny(); } - public List currentExecutions() { - return campaignExecutionRepository.currentExecutions(); - } public void stopExecution(Long executionId) { LOGGER.trace("Stop requested for " + executionId); @@ -177,7 +177,7 @@ public CampaignExecution executeScenarioInCampaign(List failedIds, Campa campaignExecution.endCampaignExecution(); LOGGER.info("Save campaign {} execution {} with status {}", campaign.id, campaignExecution.executionId, campaignExecution.status()); currentCampaignExecutionsStopRequests.remove(executionId); - campaignExecutionRepository.stopExecution(campaign.id); + campaignExecutionRepository.stopExecution(campaign.id, campaign.executionEnvironment()); Try.exec(() -> { campaignExecutionRepository.saveCampaignExecution(campaign.id, campaignExecution); @@ -303,7 +303,7 @@ private CampaignExecution executeCampaign(Campaign campaign, String userId) { } private void verifyNotAlreadyRunning(Campaign campaign) { - Optional currentReport = currentExecution(campaign.id); + Optional currentReport = currentExecution(campaign.id, campaign.executionEnvironment()); if (currentReport.isPresent() && !currentReport.get().status().isFinal()) { throw new CampaignAlreadyRunningException(currentReport.get()); } diff --git a/chutney/server/src/test/java/com/chutneytesting/campaign/infra/FakeCampaignRepository.java b/chutney/server/src/test/java/com/chutneytesting/campaign/infra/FakeCampaignRepository.java index 3eebb4da6..55a272ec1 100644 --- a/chutney/server/src/test/java/com/chutneytesting/campaign/infra/FakeCampaignRepository.java +++ b/chutney/server/src/test/java/com/chutneytesting/campaign/infra/FakeCampaignRepository.java @@ -16,6 +16,7 @@ package com.chutneytesting.campaign.infra; +import static java.util.Collections.*; import static java.util.Optional.ofNullable; import static org.assertj.core.util.Lists.newArrayList; @@ -30,7 +31,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Random; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; @@ -139,15 +139,9 @@ public List findCampaignsByScenarioId(String scenarioId) { } @Override - public Optional currentExecution(Long campaignId) { + public List currentExecutions(Long campaignId) { // not needed in tests - return Optional.empty(); - } - - @Override - public List currentExecutions() { - // not needed in tests - return null; + return emptyList(); } @Override @@ -156,7 +150,7 @@ public void startExecution(Long campaignId, CampaignExecution campaignExecution) } @Override - public void stopExecution(Long campaignId) { + public void stopExecution(Long campaignId, String environment) { // not needed in tests } diff --git a/chutney/server/src/test/java/com/chutneytesting/execution/domain/campaign/CampaignExecutionEngineTest.java b/chutney/server/src/test/java/com/chutneytesting/execution/domain/campaign/CampaignExecutionEngineTest.java index dcb7e1744..9f2b57657 100644 --- a/chutney/server/src/test/java/com/chutneytesting/execution/domain/campaign/CampaignExecutionEngineTest.java +++ b/chutney/server/src/test/java/com/chutneytesting/execution/domain/campaign/CampaignExecutionEngineTest.java @@ -25,6 +25,7 @@ import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.AdditionalMatchers.or; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; @@ -60,6 +61,7 @@ import com.chutneytesting.server.core.domain.scenario.campaign.CampaignExecution; import com.fasterxml.jackson.databind.ObjectMapper; import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; import java.util.Random; import java.util.concurrent.ExecutorService; @@ -266,17 +268,31 @@ public void should_throw_when_no_campaign_found_on_execute_by_id() { } @Test - public void should_throw_when_campaign_already_running() { - Campaign campaign = createCampaign(1L); + public void should_throw_when_campaign_already_running_on_the_same_env() { + String env = "env"; + Campaign campaign = createCampaign(1L, env); - CampaignExecution mockReport = new CampaignExecution(1L, "", false, "", null, null, ""); - when(campaignExecutionRepository.currentExecution(1L)).thenReturn(Optional.of(mockReport)); + CampaignExecution mockReport = new CampaignExecution(1L,"", false, env, null, null, ""); + when(campaignExecutionRepository.currentExecutions(1L)).thenReturn(List.of(mockReport)); // When - assertThatThrownBy(() -> sut.executeScenarioInCampaign(null, campaign, "user")) + assertThatThrownBy(() -> sut.executeScenarioInCampaign(emptyList(), campaign, "user")) .isInstanceOf(CampaignAlreadyRunningException.class); } + @Test + public void should_execute_campaign_in_parallel_on_two_different_envs() { + String env = "env"; + String otherEnv = "otherEnv"; + Campaign campaign = createCampaign(1L, env); + + CampaignExecution mockReport = new CampaignExecution(1L,"", false, otherEnv, null, null, ""); + when(campaignExecutionRepository.currentExecutions(anyLong())).thenReturn(List.of(mockReport)); + + // When + assertDoesNotThrow(()-> sut.executeScenarioInCampaign(emptyList(), campaign, "user")); + } + @Test public void should_generate_campaign_execution_id_when_executed() { // Given @@ -364,16 +380,20 @@ public void should_execute_campaign_with_given_environment_when_executed_by_name } @Test - public void should_retrieve_current_campaign_executions() { - CampaignExecution report = new CampaignExecution(1L, 33L, emptyList(), "", false, "", null, null, ""); - CampaignExecution report2 = new CampaignExecution(2L, 42L, emptyList(), "", false, "", null, null, ""); - when(campaignExecutionRepository.currentExecution(1L)).thenReturn(Optional.of(report)); - when(campaignExecutionRepository.currentExecution(2L)).thenReturn(Optional.of(report2)); + public void should_retrieve_current_campaign_execution_on_a_given_env() { + String env = "env"; + CampaignExecution report = new CampaignExecution(1L, 33L, emptyList(), "", false, env, null, null, ""); + String otherEnv = "otherEnv"; + CampaignExecution report2 = new CampaignExecution(2L, 33L, emptyList(), "", false, otherEnv, null, null, ""); + CampaignExecution report3 = new CampaignExecution(3L, 42L, emptyList(), "", false, otherEnv, null, null, ""); + when(campaignExecutionRepository.currentExecutions(33L)).thenReturn(List.of(report, report2)); + when(campaignExecutionRepository.currentExecutions(42L)).thenReturn(List.of(report3)); - Optional campaignExecutionReport = sut.currentExecution(1L); + Optional campaignExecutionReport = sut.currentExecution(33L, env); assertThat(campaignExecutionReport).isNotEmpty(); - assertThat(campaignExecutionReport.get().campaignId).isEqualTo(33L); + assertThat(campaignExecutionReport.get().executionId).isEqualTo(1L); + assertThat(campaignExecutionReport.get().executionEnvironment).isEqualTo(env); } @Test @@ -463,8 +483,8 @@ private Campaign createCampaign() { return new Campaign(generateId(), "...", null, null, "campaignEnv", false, false, null, null); } - private Campaign createCampaign(Long idCampaign) { - return new Campaign(idCampaign, "campaign1", null, emptyList(), "env", false, false, null, null); + private Campaign createCampaign(Long idCampaign, String env) { + return new Campaign(idCampaign, "campaign1", null, emptyList(), env, false, false, null, null); } private Campaign createCampaign(TestCase firstTestCase, TestCase secondtTestCase) { diff --git a/chutney/ui/src/app/modules/campaign/components/execution/history/campaign-executions-history.component.ts b/chutney/ui/src/app/modules/campaign/components/execution/history/campaign-executions-history.component.ts index c9f76f996..ba2c8d7c2 100644 --- a/chutney/ui/src/app/modules/campaign/components/execution/history/campaign-executions-history.component.ts +++ b/chutney/ui/src/app/modules/campaign/components/execution/history/campaign-executions-history.component.ts @@ -125,11 +125,9 @@ export class CampaignExecutionsHistoryComponent implements OnInit, OnDestroy { } private refreshCampaign() { - if (!this.isRefreshActive()) { - this.campaign$().subscribe(c => { - this.openReport({ execution: this.campaignReports[0], focus: true }); - }); - } + this.campaign$().subscribe(c => { + this.openReport({ execution: this.campaignReports[0], focus: true }); + }); } private onMenuError(menuErrorEvent: any) {