/*
* Copyright 2017 ThoughtWorks, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.thoughtworks.go.server.service;
import com.thoughtworks.go.config.*;
import com.thoughtworks.go.config.materials.MaterialConfigs;
import com.thoughtworks.go.config.materials.dependency.DependencyMaterial;
import com.thoughtworks.go.config.materials.git.GitMaterial;
import com.thoughtworks.go.config.materials.mercurial.HgMaterial;
import com.thoughtworks.go.config.materials.perforce.P4Material;
import com.thoughtworks.go.config.materials.svn.SvnMaterial;
import com.thoughtworks.go.domain.*;
import com.thoughtworks.go.domain.buildcause.BuildCause;
import com.thoughtworks.go.domain.feed.Author;
import com.thoughtworks.go.domain.feed.FeedEntries;
import com.thoughtworks.go.domain.feed.stage.StageFeedEntry;
import com.thoughtworks.go.domain.materials.Material;
import com.thoughtworks.go.domain.materials.Modification;
import com.thoughtworks.go.dto.DurationBean;
import com.thoughtworks.go.fixture.PipelineWithMultipleStages;
import com.thoughtworks.go.helper.*;
import com.thoughtworks.go.presentation.pipelinehistory.StageHistoryEntry;
import com.thoughtworks.go.presentation.pipelinehistory.StageHistoryPage;
import com.thoughtworks.go.remote.AgentIdentifier;
import com.thoughtworks.go.server.cache.GoCache;
import com.thoughtworks.go.server.dao.DatabaseAccessHelper;
import com.thoughtworks.go.server.dao.JobInstanceDao;
import com.thoughtworks.go.server.dao.PipelineSqlMapDao;
import com.thoughtworks.go.server.dao.StageDao;
import com.thoughtworks.go.server.domain.StageIdentity;
import com.thoughtworks.go.server.domain.StageStatusListener;
import com.thoughtworks.go.server.domain.Username;
import com.thoughtworks.go.server.materials.DependencyMaterialUpdateNotifier;
import com.thoughtworks.go.server.messaging.GoMessageListener;
import com.thoughtworks.go.server.messaging.JobResultMessage;
import com.thoughtworks.go.server.messaging.JobResultTopic;
import com.thoughtworks.go.server.persistence.MaterialRepository;
import com.thoughtworks.go.server.scheduling.ScheduleHelper;
import com.thoughtworks.go.server.service.result.HttpOperationResult;
import com.thoughtworks.go.server.transaction.TransactionSynchronizationManager;
import com.thoughtworks.go.server.transaction.TransactionTemplate;
import com.thoughtworks.go.server.ui.MingleCard;
import com.thoughtworks.go.server.ui.StageSummaryModels;
import com.thoughtworks.go.server.util.Pagination;
import com.thoughtworks.go.util.GoConfigFileHelper;
import com.thoughtworks.go.util.GoConstants;
import com.thoughtworks.go.util.ReflectionUtil;
import com.thoughtworks.go.util.TimeProvider;
import com.thoughtworks.go.utils.Assertions;
import com.thoughtworks.go.utils.Timeout;
import org.hamcrest.core.Is;
import org.joda.time.DateTime;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import static com.thoughtworks.go.domain.JobResult.Passed;
import static com.thoughtworks.go.helper.BuildPlanMother.withBuildPlans;
import static com.thoughtworks.go.helper.JobInstanceMother.building;
import static com.thoughtworks.go.helper.JobInstanceMother.completed;
import static com.thoughtworks.go.helper.ModificationsMother.checkinWithComment;
import static com.thoughtworks.go.helper.ModificationsMother.modifyOneFile;
import static com.thoughtworks.go.util.DataStructureUtils.a;
import static com.thoughtworks.go.util.SystemUtil.currentWorkingDirectory;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath:WEB-INF/applicationContext-global.xml",
"classpath:WEB-INF/applicationContext-dataLocalAccess.xml",
"classpath:WEB-INF/applicationContext-acegi-security.xml"
})
public class StageServiceIntegrationTest {
@Autowired private JobInstanceDao jobInstanceDao;
@Autowired private StageService stageService;
@Autowired private AgentService agentService;
@Autowired private StageDao stageDao;
@Autowired private PipelineSqlMapDao pipelineDao;
@Autowired private DatabaseAccessHelper dbHelper;
@Autowired private ScheduleHelper scheduleHelper;
@Autowired private ScheduleService scheduleService;
@Autowired private GoConfigDao goConfigDao;
@Autowired private JobResultTopic jobResultTopic;
@Autowired private TransactionTemplate transactionTemplate;
@Autowired private TransactionSynchronizationManager transactionSynchronizationManager;
@Autowired private MaterialRepository materialRepository;
@Autowired private ConfigDbStateRepository configDbStateRepository;
@Autowired private GoConfigService goConfigService;
@Autowired private ChangesetService changesetService;
@Autowired private GoCache goCache;
@Autowired private InstanceFactory instanceFactory;
@Autowired private DependencyMaterialUpdateNotifier notifier;
private static final String PIPELINE_NAME = "mingle";
private static final String STAGE_NAME = "dev";
private JobInstance job;
private static final String UUID = "uuid";
private Pipeline savedPipeline;
private PipelineConfig pipelineConfig;
private Stage stage;
private GoConfigFileHelper configFileHelper = new GoConfigFileHelper();
private PipelineWithMultipleStages fixture;
private String md5 = "md5-test";
private JobState receivedState;
private JobResult receivedResult;
private StageResult receivedStageResult;
@Before
public void setUp() throws Exception {
dbHelper.onSetUp();
pipelineConfig = PipelineMother.withSingleStageWithMaterials(PIPELINE_NAME, STAGE_NAME, withBuildPlans("unit", "dev", "blah"));
pipelineConfig.getFirstStageConfig().setFetchMaterials(false);
pipelineConfig.getFirstStageConfig().setCleanWorkingDir(true);
configFileHelper.usingCruiseConfigDao(goConfigDao);
configFileHelper.onSetUp();
configFileHelper.addPipeline(PIPELINE_NAME, STAGE_NAME);
savedPipeline = scheduleHelper.schedule(pipelineConfig, BuildCause.createWithModifications(modifyOneFile(pipelineConfig), ""), GoConstants.DEFAULT_APPROVED_BY);
stage = savedPipeline.getStages().first();
job = stage.getJobInstances().first();
job.setAgentUuid(UUID);
jobInstanceDao.updateAssignedInfo(job);
AgentIdentifier agentIdentifier = new AgentIdentifier("localhost", "127.0.0.1", UUID);
agentService.updateRuntimeInfo(new AgentRuntimeInfo(agentIdentifier, AgentRuntimeStatus.Idle, currentWorkingDirectory(), "cookie", false));
receivedState = null;
receivedResult = null;
receivedStageResult = null;
notifier.disableUpdates();
}
@After
public void teardown() throws Exception {
if (fixture != null) {
fixture.onTearDown();
}
dbHelper.onTearDown();
configFileHelper.onTearDown();
notifier.enableUpdates();
}
@Test
public void shouldPostAllMessagesAfterTheDatabaseIsUpdatedWhenCancellingAStage() {
jobResultTopic.addListener(new GoMessageListener<JobResultMessage>() {
public void onMessage(JobResultMessage message) {
JobIdentifier jobIdentifier = message.getJobIdentifier();
JobInstance instance = jobInstanceDao.mostRecentJobWithTransitions(jobIdentifier);
receivedState = instance.getState();
receivedResult = instance.getResult();
}
});
stageService.addStageStatusListener(new StageStatusListener() {
public void stageStatusChanged(Stage stage) {
Stage retrievedStage = stageDao.stageById(stage.getId());
receivedStageResult = retrievedStage.getResult();
}
});
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
stageService.cancelStage(stage);
}
});
Assertions.waitUntil(Timeout.TEN_SECONDS, new Assertions.Predicate() {
public boolean call() throws Exception {
return receivedResult != null && receivedState != null && receivedStageResult!=null;
}
});
assertThat(receivedState, is(JobState.Completed));
assertThat(receivedResult, is(JobResult.Cancelled));
assertThat(receivedStageResult, is(StageResult.Cancelled));
}
@Test
public void shouldReturnFalseWhenAllStagesAreCompletedInAGivenPipeline() throws Exception {
fixture = new PipelineWithMultipleStages(4, materialRepository, transactionTemplate);
fixture.usingConfigHelper(configFileHelper).usingDbHelper(dbHelper).onSetUp();
Pipeline pipeline = fixture.createdPipelineWithAllStagesPassed();
assertThat(stageService.isAnyStageActiveForPipeline(fixture.pipelineName, pipeline.getCounter()), Is.is(false));
}
@Test
public void shouldReturnTrueIfAnyStageIsBuildingInAGivenPipeline() throws Exception {
fixture = new PipelineWithMultipleStages(4, materialRepository, transactionTemplate);
fixture.usingConfigHelper(configFileHelper).usingDbHelper(dbHelper).onSetUp();
Pipeline pipeline = fixture.createPipelineWithFirstStageAssigned();
assertThat(stageService.isAnyStageActiveForPipeline(fixture.pipelineName, pipeline.getCounter()), Is.is(true));
}
@Test
public void shouldReturnStageWithSpecificCounter() throws Exception {
Stage firstStage = savedPipeline.getStages().first();
Stage newInstance = instanceFactory.createStageInstance(pipelineConfig.first(), new DefaultSchedulingContext(
"anonymous"), md5, new TimeProvider());
Stage newSavedStage = stageService.save(savedPipeline, newInstance);
Stage latestStage = stageService.findStageWithIdentifier(
new StageIdentifier(CaseInsensitiveString.str(pipelineConfig.name()), null, savedPipeline.getLabel(), firstStage.getName(), String.valueOf(newSavedStage.getCounter())));
assertThat(latestStage, is(newSavedStage));
}
@Test
public void shouldReturnStageWithSpecificCounter_findStageWithIdentifier() throws Exception {
Stage newInstance = instanceFactory.createStageInstance(pipelineConfig.first(), new DefaultSchedulingContext("anonymous"), md5, new TimeProvider());
Stage newSavedStage = stageService.save(savedPipeline, newInstance);
StageIdentifier identifier = newSavedStage.getIdentifier();
Stage latestStage = stageService.findStageWithIdentifier(identifier.getPipelineName(), identifier.getPipelineCounter(), identifier.getStageName(), identifier.getStageCounter(), "admin", new HttpOperationResult());
assertThat(latestStage, is(newSavedStage));
}
@Test
public void shouldReturnTrueIfStageIsActive() throws Exception {
savedPipeline.getStages().first();
Stage newInstance = instanceFactory.createStageInstance(pipelineConfig.first(), new DefaultSchedulingContext("anonymous"), md5, new TimeProvider());
stageService.save(savedPipeline, newInstance);
boolean stageActive = stageService.isStageActive(CaseInsensitiveString.str(pipelineConfig.name()), CaseInsensitiveString.str(pipelineConfig.first().name()));
assertThat(stageActive, is(true));
}
@Test
public void shouldSaveStageWithFetchMaterialsFlag() throws Exception {
Stage firstStage = savedPipeline.getStages().first();
Stage savedStage = stageService.stageById(firstStage.getId());
assertThat(savedStage.shouldFetchMaterials(), is(false));
}
@Test
public void shouldSaveStageWithCleanWorkingDirFlag() throws Exception {
Stage firstStage = savedPipeline.getStages().first();
Stage savedStage = stageService.stageById(firstStage.getId());
assertThat(savedStage.shouldCleanWorkingDir(), is(true));
}
@Test
public void shouldGetStageHistoryForCurrentPage() throws Exception {
StageHistoryEntry[] stages = createFiveStages();
StageHistoryPage history = stageService.findStageHistoryPage(stageService.stageById(stages[2].getId()), 3);
assertThat("Found " + history, history.getPagination(), is(Pagination.pageStartingAt(0, 5, 3)));
assertThat("Found " + history, history.getStages().size(), is(3));
assertThat("Found " + history, history.getStages().get(0), is(stages[4]));
assertThat("Found " + history, history.getStages().get(1), is(stages[3]));
assertThat("Found " + history, history.getStages().get(2), is(stages[2]));
}
@Test
public void shouldGetStageHistoryForLastPage() throws Exception {
StageHistoryEntry[] stages = createFiveStages();
StageHistoryPage history = stageService.findStageHistoryPage(stageService.stageById(stages[0].getId()), 3);
assertThat("Found " + history, history.getStages().get(0), is(stages[1]));
assertThat("Found " + history, history.getStages().get(1), is(stages[0]));
assertThat("Found " + history, history.getPagination(), is(Pagination.pageStartingAt(3, 5, 3)));
assertThat("Found " + history, history.getPagination().getCurrentPage(), is(2));
}
private StageHistoryEntry[] createFiveStages() {
Stage[] stages = new Stage[5];
stages[0] = savedPipeline.getFirstStage();
for (int i = 1; i < stages.length; i++) {
DefaultSchedulingContext ctx = new DefaultSchedulingContext("anonumous");
StageConfig stageCfg = pipelineConfig.first();
stages[i] = i%2 == 0 ? instanceFactory.createStageInstance(stageCfg, ctx, md5, new TimeProvider()) : instanceFactory.createStageForRerunOfJobs(stages[i - 1], a("unit", "blah"), ctx,
stageCfg, new TimeProvider(), "md5");
stageService.save(savedPipeline, stages[i]);
}
StageHistoryEntry[] entries = new StageHistoryEntry[5];
for (int i = 0; i < stages.length; i++) {
entries[i] = new StageHistoryEntry(stages[i], pipelineDao.loadHistory(savedPipeline.getId()).getNaturalOrder(), stages[i].getRerunOfCounter());
}
return entries;
}
@Test
public void shouldGetStageHistoryByPageNumber() throws Exception {
StageHistoryEntry[] stages = createFiveStages();
StageHistoryPage history = stageService.findStageHistoryPageByNumber(PIPELINE_NAME, STAGE_NAME, 2, 3);
assertThat("Found " + history, history.getPagination(), is(Pagination.pageStartingAt(3, 5, 3)));
assertThat("Found " + history, history.getStages().size(), is(2));
assertThat("Found " + history, history.getStages().get(0), is(stages[1]));
assertThat("Found " + history, history.getStages().get(1), is(stages[0]));
}
@Test
public void shouldSaveStageWithStateBuilding() throws Exception {
Stage stage = instanceFactory.createStageInstance(pipelineConfig.first(), new DefaultSchedulingContext("anonumous"), md5, new TimeProvider());
stageService.save(savedPipeline, stage);
Stage latestStage = stageService.findLatestStage(CaseInsensitiveString.str(pipelineConfig.name()), CaseInsensitiveString.str(pipelineConfig.first().name()));
assertThat(latestStage.getState(), is(StageState.Building));
}
@Test
public void shouldIgnoreErrorsWhenNotifyingListenersDuringSave() throws Exception {
List<StageStatusListener> original = new ArrayList<>(stageService.getStageStatusListeners());
try {
stageService.getStageStatusListeners().clear();
StageStatusListener failingListener = mock(StageStatusListener.class);
doThrow(new RuntimeException("Should not be rethrown by save")).when(failingListener).stageStatusChanged(any(Stage.class));
StageStatusListener passingListener = mock(StageStatusListener.class);
stageService.getStageStatusListeners().add(failingListener);
stageService.getStageStatusListeners().add(passingListener);
Stage newInstance = instanceFactory.createStageInstance(pipelineConfig.first(), new DefaultSchedulingContext("anonumous"), md5, new TimeProvider());
Stage savedStage = stageService.save(savedPipeline, newInstance);
assertThat("Got: " + savedStage.getId(), savedStage.getId() > 0L, is(true));
verify(passingListener).stageStatusChanged(any(Stage.class));
} finally {
stageService.getStageStatusListeners().clear();
stageService.getStageStatusListeners().addAll(original);
}
}
@Test
public void shouldNotifyListenersWhenStageScheduled() throws Exception {
StageStatusListener listener = mock(StageStatusListener.class);
stageService.addStageStatusListener(listener);
Stage newInstance = instanceFactory.createStageInstance(pipelineConfig.first(),
new DefaultSchedulingContext("anonymous"), md5, new TimeProvider());
Stage savedStage = stageService.save(savedPipeline, newInstance);
verify(listener).stageStatusChanged(savedStage);
}
@Test
public void shouldNotifyListenersAfterStageIsCancelled() throws SQLException {
StageStatusListener listener = mock(StageStatusListener.class);
stageService.addStageStatusListener(listener);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
stageService.cancelStage(stage);
}
});
stage = stageService.stageById(stage.getId());
verify(listener).stageStatusChanged(stage);
}
@Test public void shouldLookupModifiedStageById_afterCancel() throws SQLException {
Stage stageLoadedBeforeCancellation = stageService.stageById(stage.getId());
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
stageService.cancelStage(stage);
}
});
assertThat(stageService.stageById(stage.getId()), is(not(stageLoadedBeforeCancellation)));
}
@Test public void shouldLookupModifiedStageById_afterJobUpdate() throws SQLException {
Stage stageLoadedBeforeUpdate = stageService.stageById(stage.getId());
dbHelper.completeAllJobs(stage, JobResult.Passed);
assertThat(stageService.stageById(stage.getId()), is(stageLoadedBeforeUpdate));
stageService.updateResult(stage);
assertThat(stageService.stageById(stage.getId()), is(not(stageLoadedBeforeUpdate)));
}
@Test public void shouldNotCancelAlreadyCompletedBuild() throws SQLException {
jobResultTopic.addListener(new GoMessageListener<JobResultMessage>() {
public void onMessage(JobResultMessage message) {
JobIdentifier jobIdentifier = message.getJobIdentifier();
JobInstance instance = jobInstanceDao.mostRecentJobWithTransitions(jobIdentifier);
receivedState = instance.getState();
receivedResult = instance.getResult();
}
});
JobInstanceMother.completed(job, Passed, new Date());
jobInstanceDao.updateStateAndResult(job);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
stageService.cancelStage(stage);
}
});
JobInstance instanceFromDB = jobInstanceDao.buildByIdWithTransitions(job.getId());
assertThat(instanceFromDB.getState(), is(JobState.Completed));
assertThat(instanceFromDB.getResult(), is(Passed));
assertThat(agentService.findAgentAndRefreshStatus(UUID).isCancelled(), is(false));
assertThat(receivedState, is(nullValue()));
assertThat(receivedResult, is(nullValue()));
}
@Test
// #2328
public void shouldGetDurationBasedOnPipelineNameStageNameJobNameAndAgentUUID() throws Exception {
String pipelineName = "Cruise";
configFileHelper.addPipeline(pipelineName, STAGE_NAME);
Stage saveStage = dbHelper.saveTestPipeline(pipelineName, STAGE_NAME).getStages().first();
JobInstance job1 = completed("unit", Passed, new Date(), new DateTime().minusMinutes(1).toDate());
job1.setAgentUuid(UUID);
jobInstanceDao.save(saveStage.getId(), job1);
String pipeline2Name = "Cruise-1.1";
configFileHelper.addPipeline(pipeline2Name, STAGE_NAME);
Stage stage11 = dbHelper.saveTestPipeline(pipeline2Name, STAGE_NAME).getStages().first();
final JobInstance job2 = building("unit", new Date());
job2.setAgentUuid(UUID);
JobInstance buildingJob = jobInstanceDao.save(stage11.getId(), job2);
final DurationBean duration = stageService.getBuildDuration("Cruise-1.1", STAGE_NAME, buildingJob);
assertThat("we should not load duration according to stage name + job name + agent uuid only, "
+ "we should also use pipeline name as a paramater", duration.getDuration(), is(0L));
}
@Test
public void shouldNotifyListenerOnStageStatusChange() throws Exception {
StageStatusListener listener = mock(StageStatusListener.class);
stageService.addStageStatusListener(listener);
stageService.updateResult(stage);
verify(listener).stageStatusChanged(stage);
}
@Test
public void findStageIdByIdentifierShouldConvertStageIdentifierIntoId() throws Exception {
dbHelper.execute("UPDATE pipelines SET label = '1.3.4' WHERE id = " + stage.getPipelineId());
dbHelper.execute("UPDATE pipelines SET counter = null WHERE id = " + stage.getPipelineId());
assertThat(stageService.findStageIdByLocator("mingle/1.3.4/dev/1"), is(stage.getId()));
}
@Test
public void findStageIdByIdentifierShouldConvertStageIdentifierWithLabelAndCounterContainingIntoId() throws Exception {
dbHelper.execute("UPDATE pipelines SET label = '1.3.4.3345' WHERE id = " + stage.getPipelineId());
dbHelper.execute("UPDATE pipelines SET counter = '3345' WHERE id = " + stage.getPipelineId());
assertThat(stageService.findStageIdByLocator("mingle/3345[1.3.4.3345]/dev/1"), is(stage.getId()));
}
@Test
public void ensurePipelineSqlMapDaoIsAStageStatusListener() {
assertThat(stageService.getStageStatusListeners(), hasItem((StageStatusListener) pipelineDao));
}
@Test
public void shouldNotNotifyListenersWhenJobCancellationTransactionRollsback() {
StageStatusListener listener = mock(StageStatusListener.class);
JobInstanceService jobInstanceService = mock(JobInstanceService.class);
JobInstance job = JobInstanceMother.building("foo");
doThrow(new RuntimeException("test exception")).when(jobInstanceService).cancelJob(job);
JobIdentifier jobId = new JobIdentifier("pipeline", 10, "label-10", "stage", "1", "foo");
job.setIdentifier(jobId);
StageDao stageDao = mock(StageDao.class);
Stage stage = StageMother.custom("stage");
when(stageDao.findStageWithIdentifier(jobId.getStageIdentifier())).thenReturn(stage);
StageService service = new StageService(stageDao, jobInstanceService, null, null, null, null, changesetService, goConfigService, transactionTemplate, transactionSynchronizationManager,
goCache, listener);
try {
service.cancelJob(job);
fail("should have thrown up when underlying service bombed");
} catch (Exception e) {
assertThat(e.getMessage(), is("test exception"));
}
verify(listener, never()).stageStatusChanged(any(Stage.class));
}
@Test
public void shouldNotifyListenersWhenJobCancelledSuccessfully() {
StageStatusListener listener = mock(StageStatusListener.class);
JobInstanceService jobInstanceService = mock(JobInstanceService.class);
JobInstance job = JobInstanceMother.building("foo");
JobIdentifier jobId = new JobIdentifier("pipeline", 10, "label-10", "stage", "1", "foo");
job.setIdentifier(jobId);
StageDao stageDao = mock(StageDao.class);
Stage stage = StageMother.custom("stage");
when(stageDao.findStageWithIdentifier(jobId.getStageIdentifier())).thenReturn(stage);
StageService service = new StageService(stageDao, jobInstanceService, null, null, null, null, changesetService, goConfigService, transactionTemplate, transactionSynchronizationManager,
goCache, listener);
service.cancelJob(job);
verify(listener).stageStatusChanged(stage);
}
@Test
public void shouldLoadStagesHavingArtifactsWhenStageIsNotCleanupProtected() {
PipelineConfig pipelineConfig = configFileHelper.addPipeline("pipeline-1", "stage-1", "job-1");
Pipeline completed = dbHelper.schedulePipelineWithAllStages(pipelineConfig, ModificationsMother.modifySomeFiles(pipelineConfig));
dbHelper.pass(completed);
List<Stage> stages = stageService.oldestStagesWithDeletableArtifacts();
assertThat(stages.size(), is(1));
CruiseConfig cruiseConfig = configFileHelper.currentConfig();
pipelineConfig = cruiseConfig.pipelineConfigByName(new CaseInsensitiveString("pipeline-1"));
ReflectionUtil.setField(pipelineConfig.get(0), "artifactCleanupProhibited", true);
configFileHelper.writeConfigFile(cruiseConfig);
configDbStateRepository.flushConfigState();
stages = stageService.oldestStagesWithDeletableArtifacts();
assertThat(stages.size(), is(0));
}
@Test
public void shouldLoadPageOfOldestStagesHavingArtifacts() {
CruiseConfig cruiseConfig = configFileHelper.currentConfig();
PipelineConfig mingleConfig = cruiseConfig.pipelineConfigByName(new CaseInsensitiveString(PIPELINE_NAME));
ReflectionUtil.setField(mingleConfig.get(0), "artifactCleanupProhibited", true);
configFileHelper.writeConfigFile(cruiseConfig);
configDbStateRepository.flushConfigState();
Pipeline[] pipelines = new Pipeline[101];
for (int i = 0; i < 101; i++) {
PipelineConfig pipelineCfg = configFileHelper.addPipeline("pipeline-" + i, "stage", "job");
Pipeline pipeline = dbHelper.schedulePipeline(pipelineCfg, new TimeProvider());
dbHelper.pass(pipeline);
pipelines[i] = pipeline;
}
List<Stage> stages = stageService.oldestStagesWithDeletableArtifacts();
for (int i = 0; i < 100; i++) {
Stage stage = stages.get(i);
assertThat(stage.getIdentifier(), is(pipelines[i].getFirstStage().getIdentifier()));
stageService.markArtifactsDeletedFor(stage);
}
assertThat(stages.size(), is(100));
stages = stageService.oldestStagesWithDeletableArtifacts();
assertThat(stages.size(), is(1));
Stage stage = stages.get(0);
assertThat(stage.getIdentifier(), is(pipelines[100].getFirstStage().getIdentifier()));
stageService.markArtifactsDeletedFor(stage);
assertThat(stageService.oldestStagesWithDeletableArtifacts().size(), is(0));
}
@Test
public void findStageHistoryForChart_shouldFindLatestStageInstancesForChart() throws Exception {
PipelineConfig pipelineConfig = configFileHelper.addPipeline("pipeline-1", "stage-1");
configFileHelper.turnOffSecurity();
List<Pipeline> completedPipelines = new ArrayList<>();
Pipeline pipeline;
for (int i = 0; i < 16; i++) {
pipeline = dbHelper.schedulePipelineWithAllStages(pipelineConfig, ModificationsMother.modifySomeFiles(pipelineConfig));
dbHelper.pass(pipeline);
completedPipelines.add(pipeline);
}
StageSummaryModels stages = stageService.findStageHistoryForChart(pipelineConfig.name().toString(), pipelineConfig.first().name().toString(), 1, 4, new Username(new CaseInsensitiveString("loser")));
assertThat(stages.size(), is(4));
assertThat(stages.get(0).getIdentifier().getPipelineCounter(), is(16));
stages = stageService.findStageHistoryForChart(pipelineConfig.name().toString(), pipelineConfig.first().name().toString(), 3, 4, new Username(new CaseInsensitiveString("loser")));
assertThat(stages.size(), is(4));
assertThat(stages.get(0).getIdentifier().getPipelineCounter(), is(8));
assertThat(stages.getPagination().getTotalPages(), is(4));
}
@Test
public void findStageHistoryForChart_shouldNotRetrieveCancelledStagesAndStagesWithRerunJobs() throws Exception {
PipelineConfig pipelineConfig = configFileHelper.addPipeline("pipeline-1", "stage-1");
configFileHelper.turnOffSecurity();
Pipeline pipeline = dbHelper.schedulePipelineWithAllStages(pipelineConfig, ModificationsMother.modifySomeFiles(pipelineConfig));
dbHelper.pass(pipeline);
StageSummaryModels stages = stageService.findStageHistoryForChart(pipelineConfig.name().toString(), pipelineConfig.first().name().toString(), 1, 10, new Username(new CaseInsensitiveString("loser")));
assertThat(stages.size(), Is.is(1));
scheduleService.rerunJobs(pipeline.getFirstStage(), Arrays.asList(CaseInsensitiveString.str(pipelineConfig.first().getJobs().first().name())), new HttpOperationResult());
stages = stageService.findStageHistoryForChart(pipelineConfig.name().toString(), pipelineConfig.first().name().toString(), 1, 10, new Username(new CaseInsensitiveString("loser")));
assertThat(stages.size(), Is.is(1)); //should not retrieve stages with rerun jobs
pipeline = dbHelper.schedulePipelineWithAllStages(pipelineConfig, ModificationsMother.modifySomeFiles(pipelineConfig));
dbHelper.cancelStage(pipeline.getFirstStage());
stages = stageService.findStageHistoryForChart(pipelineConfig.name().toString(), pipelineConfig.first().name().toString(), 1, 10, new Username(new CaseInsensitiveString("loser")));
assertThat(stages.size(), Is.is(1)); //should not retrieve cancelled stages
}
@Test
public void shouldSaveTheStageStatusProperlyUponJobCancelAfterInvalidatingTheCache() throws Exception {
fixture = (PipelineWithMultipleStages) new PipelineWithMultipleStages(2, materialRepository, transactionTemplate).usingTwoJobs();
fixture.usingConfigHelper(configFileHelper).usingDbHelper(dbHelper).onSetUp();
Pipeline pipeline = fixture.createPipelineWithFirstStageAssigned();
Stage currentStage = pipeline.getFirstStage();
JobInstances jobs = currentStage.getJobInstances();
JobInstance firstJob = jobs.first();
firstJob.completing(JobResult.Passed);
firstJob.completed(new Date());
jobInstanceDao.updateStateAndResult(firstJob);
// prime the cache
stageService.findStageWithIdentifier(new StageIdentifier(pipeline, currentStage));
stageService.cancelJob(jobs.last());
Stage savedStage = stageService.stageById(currentStage.getId());
assertThat(savedStage.getState(), is(StageState.Cancelled));
assertThat(savedStage.getResult(), is(StageResult.Cancelled));
}
@Test
public void shouldNotLoadStageAuthorsAndMingleCards_fromUpstreamInvisibleToUser() throws Exception {
MingleConfig upstreamMingle = new MingleConfig("https://mingle.gingle/when/single", "jingle");
MingleConfig downstreamMingle = new MingleConfig("https://downstream-mingle", "go");
PipelineConfig downstream = setup2DependentInstances(upstreamMingle, downstreamMingle);
configFileHelper.addSecurityWithPasswordFile();
configFileHelper.addAdmins("super-hero");
configFileHelper.addAuthorizedUserForPipelineGroup("loser", "upstream-without-mingle");
configFileHelper.addAuthorizedUserForPipelineGroup("loser", "downstream");
configFileHelper.addAuthorizedUserForPipelineGroup("boozer", "upstream-with-mingle");
FeedEntries feed = stageService.feed(downstream.name().toString(), new Username(new CaseInsensitiveString("loser")));
assertAuthorsAndCardsOnEntry((StageFeedEntry) feed.get(0),
Arrays.asList(new Author("svn 3 guy", "svn.3@gmail.com"),
new Author("p4 2 guy", "p4.2@gmail.com")),
Arrays.asList(new MingleCard(downstreamMingle, "103")));
assertAuthorsAndCardsOnEntry((StageFeedEntry) feed.get(1),
Arrays.asList(new Author("svn 1 guy", "svn.1@gmail.com"),
new Author("svn 2 guy", "svn.2@gmail.com"),
new Author("p4 1 guy", "p4.1@gmail.com")),
Arrays.asList(new MingleCard(downstreamMingle, "101"),
new MingleCard(downstreamMingle, "102")));
}
@Test
public void shouldLoadStageAuthorsAndMingleCards_forFirstPageOfFeed() throws Exception {
MingleConfig upstreamMingle = new MingleConfig("https://mingle.gingle/when/single", "jingle");
MingleConfig downstreamMingle = new MingleConfig("https://downstream-mingle", "go");
PipelineConfig downstream = setup2DependentInstances(upstreamMingle, downstreamMingle);
FeedEntries feed = stageService.feed(downstream.name().toString(), new Username(new CaseInsensitiveString("loser")));
assertStageEntryAuthorAndMingleCards(upstreamMingle, downstreamMingle, feed);
}
@Test
public void shouldLoadStageAuthorsAndMingleCards_forSubsequentPages() throws Exception {
MingleConfig upstreamMingle = new MingleConfig("https://mingle.gingle/when/single", "jingle");
MingleConfig downstreamMingle = new MingleConfig("https://downstream-mingle", "go");
PipelineConfig downstream = setup2DependentInstances(upstreamMingle, downstreamMingle);
FeedEntries feed = stageService.feedBefore(Integer.MAX_VALUE, downstream.name().toString(), new Username(new CaseInsensitiveString("loser")));
assertStageEntryAuthorAndMingleCards(upstreamMingle, downstreamMingle, feed);
}
@Test
public void shouldFetchLatestStageInstanceForEachStage() {
MingleConfig upstreamMingle = new MingleConfig("https://mingle.gingle/when/single", "jingle");
MingleConfig downstreamMingle = new MingleConfig("https://downstream-mingle", "go");
setup2DependentInstances(upstreamMingle, downstreamMingle);
List<StageIdentity> latestStageInstances = stageService.findLatestStageInstances();
assertThat(latestStageInstances.size(), is(4));
assertThat(latestStageInstances.contains(new StageIdentity("mingle", "dev",8L)),is(true));
assertThat(latestStageInstances.contains(new StageIdentity("upstream-without-mingle", "stage",13L)),is(true));
assertThat(latestStageInstances.contains(new StageIdentity("downstream", "down-stage",14L)),is(true));
assertThat(latestStageInstances.contains(new StageIdentity("upstream-with-mingle", "stage",10L)),is(true));
}
private void assertStageEntryAuthorAndMingleCards(MingleConfig upstreamMingle, MingleConfig downstreamMingle, FeedEntries feed) {
assertAuthorsAndCardsOnEntry((StageFeedEntry) feed.get(0),
Arrays.asList(new Author("hg 3 guy", "hg.3@gmail.com"),
new Author("git 2&3 guy", "git.2.and.3@gmail.com"),
new Author("svn 3 guy", "svn.3@gmail.com"),
new Author("p4 2 guy", "p4.2@gmail.com")),
Arrays.asList(new MingleCard(downstreamMingle, "103"),
new MingleCard(upstreamMingle, "303"),
new MingleCard(upstreamMingle, "402"),
new MingleCard(upstreamMingle, "403")));
assertAuthorsAndCardsOnEntry((StageFeedEntry) feed.get(1),
Arrays.asList(new Author("hg 1 guy", "hg.1@gmail.com"),
new Author("hg 2 guy", null),
new Author("git 1 guy", "git.1@gmail.com"),
new Author("svn 1 guy", "svn.1@gmail.com"),
new Author("svn 2 guy", "svn.2@gmail.com"),
new Author("p4 1 guy", "p4.1@gmail.com")),
Arrays.asList(new MingleCard(downstreamMingle, "101"),
new MingleCard(downstreamMingle, "102"),
new MingleCard(upstreamMingle, "301"),
new MingleCard(upstreamMingle, "302"),
new MingleCard(upstreamMingle, "401")));
}
private void assertAuthorsAndCardsOnEntry(StageFeedEntry stage2, List<Author> authors, List<MingleCard> cards) {
List<Author> stage2Authors = stage2.getAuthors();
assertThat(stage2Authors, hasItems(authors.toArray(new Author[0])));
assertThat(stage2Authors.size(), is(authors.size()));
List<MingleCard> stage2cards = stage2.getMingleCards();
assertThat(stage2cards, hasItems(cards.toArray(new MingleCard[0])));
assertThat(stage2cards.size(), is(cards.size()));
}
private PipelineConfig setup2DependentInstances(MingleConfig upstreamMingle, MingleConfig downstreamMingle) {
Username loser = new Username(new CaseInsensitiveString("loser"));
ManualBuild build = new ManualBuild(loser);
Date checkinTime = new Date();
GitMaterial git = MaterialsMother.gitMaterial("http://google.com", null, "master");
git.setFolder("git");
HgMaterial hg = MaterialsMother.hgMaterial();
hg.setFolder("hg");
PipelineConfig upstreamWithMingle = PipelineConfigMother.createPipelineConfig("upstream-with-mingle", "stage", "build");
upstreamWithMingle.setMaterialConfigs(new MaterialConfigs(git.config(), hg.config()));
upstreamWithMingle.setMingleConfig(upstreamMingle);
configFileHelper.addPipelineToGroup(upstreamWithMingle, "upstream-with-mingle");
P4Material p4 = MaterialsMother.p4Material("loser:007", "loser", "boozer", "through-the-window", true);
PipelineConfig upstreamWithoutMingle = PipelineConfigMother.createPipelineConfig("upstream-without-mingle", "stage", "build");
upstreamWithoutMingle.setMaterialConfigs(new MaterialConfigs(p4.config()));
configFileHelper.addPipelineToGroup(upstreamWithoutMingle, "upstream-without-mingle");
DependencyMaterial dependencyMaterial = MaterialsMother.dependencyMaterial(upstreamWithMingle.name().toString(), upstreamWithMingle.get(0).name().toString());
SvnMaterial svn = MaterialsMother.svnMaterial("http://svn.com");
DependencyMaterial dependencyMaterialViaP4 = MaterialsMother.dependencyMaterial(upstreamWithoutMingle.name().toString(), upstreamWithoutMingle.get(0).name().toString());
PipelineConfig downstream = PipelineConfigMother.createPipelineConfig("downstream", "down-stage", "job");
downstream.setMaterialConfigs(new MaterialConfigs(dependencyMaterial.config(), svn.config(), dependencyMaterialViaP4.config()));
downstream.setMingleConfig(downstreamMingle);
configFileHelper.addPipelineToGroup(downstream, "downstream");
//mingle card nos.
//svn: 1xx
//p4: 2xx
//hg: 3xx
//git: 4xx
Modification svnCommit1 = checkin(svn, "888", "svn commit #101 1", "svn 1 guy", "svn.1@gmail.com", checkinTime);
Modification hgCommit1 = checkin(hg, "abcd", "hg commit 1 #301", "hg 1 guy", "hg.1@gmail.com", checkinTime);
Modification gitCommit1 = checkin(git, "1234", "#401 - git commit 1", "git 1 guy", "git.1@gmail.com", checkinTime);
Modification gitCommit2 = checkin(git, "2355", "git #402 commit 2", "git 2&3 guy", "git.2.and.3@gmail.com", checkinTime);//used in a later instance
Modification hgCommit2 = checkin(hg, "abc", "hg commit #302 2", "hg 2 guy", null, checkinTime);
Pipeline pipelineOne = dbHelper.checkinRevisionsToBuild(build, upstreamWithMingle, new MaterialRevision(git, gitCommit1), new MaterialRevision(hg, hgCommit2, hgCommit1));
Modification hgCommit3 = checkin(hg, "bcde", "hg commit 3 #303", "hg 3 guy", "hg.3@gmail.com", checkinTime);
Modification gitCommit3 = checkin(git, "567", "git #403 commit 3", "git 2&3 guy", "git.2.and.3@gmail.com", checkinTime);
Pipeline pipelineTwo = dbHelper.checkinRevisionsToBuild(build, upstreamWithMingle, new MaterialRevision(git, gitCommit3, gitCommit2), new MaterialRevision(hg, hgCommit3));
Modification p4Commit1 = checkin(p4, "777", "#201 - p4 commit 1", "p4 1 guy", "p4.1@gmail.com", checkinTime);
Pipeline pipelineP4 = dbHelper.checkinRevisionsToBuild(build, upstreamWithoutMingle, new MaterialRevision(p4, p4Commit1));
ArrayList<MaterialRevision> materialRevisionsFor1 = new ArrayList<>();
dbHelper.addDependencyRevisionModification(materialRevisionsFor1, dependencyMaterial, pipelineOne);
dbHelper.addDependencyRevisionModification(materialRevisionsFor1, dependencyMaterialViaP4, pipelineP4);
Modification svnCommit2 = checkin(svn, "999", "svn #102 commit 2", "svn 2 guy", "svn.2@gmail.com", checkinTime);
materialRevisionsFor1.add(new MaterialRevision(svn, svnCommit2, svnCommit1));
//save downstream pipeline 1
dbHelper.passPipeline(dbHelper.checkinRevisionsToBuild(build, downstream, materialRevisionsFor1));
Modification p4Commit2 = checkin(p4, "007", "#202 - p4 commit 2", "p4 2 guy", "p4.2@gmail.com", checkinTime);
Pipeline pipeline2P4 = dbHelper.checkinRevisionsToBuild(build, upstreamWithoutMingle, new MaterialRevision(p4, p4Commit2));
ArrayList<MaterialRevision> materialRevisionsFor2 = new ArrayList<>();
dbHelper.addDependencyRevisionModification(materialRevisionsFor2, dependencyMaterial, pipelineTwo);
dbHelper.addDependencyRevisionModification(materialRevisionsFor2, dependencyMaterialViaP4, pipeline2P4);
Modification svnCommit3 = checkin(svn, "1000", "svn commit #103 3", "svn 3 guy", "svn.3@gmail.com", checkinTime);
materialRevisionsFor2.add(new MaterialRevision(svn, svnCommit3));
//save downstream pipeline 2
dbHelper.passPipeline(dbHelper.checkinRevisionsToBuild(build, downstream, materialRevisionsFor2));
return downstream;
}
private Modification checkin(Material material, String rev, String comment, String user, String email, Date checkinTime) {
Modification commit = checkinWithComment(rev, comment, user, email, checkinTime);
dbHelper.addRevisionsWithModifications(material, commit);//saved now, used later
return commit;
}
}