/* * 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.CaseInsensitiveString; import com.thoughtworks.go.config.GoConfigDao; import com.thoughtworks.go.config.PipelineConfig; import com.thoughtworks.go.config.StageConfig; import com.thoughtworks.go.domain.*; import com.thoughtworks.go.domain.buildcause.BuildCause; import com.thoughtworks.go.domain.exception.StageAlreadyBuildingException; import com.thoughtworks.go.domain.materials.Material; import com.thoughtworks.go.domain.materials.Modification; import com.thoughtworks.go.domain.materials.svn.Subversion; import com.thoughtworks.go.domain.materials.svn.SvnCommand; import com.thoughtworks.go.helper.ModificationsMother; import com.thoughtworks.go.helper.PipelineMother; import com.thoughtworks.go.helper.SvnTestRepo; import com.thoughtworks.go.helper.TestRepo; 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.PipelineDao; import com.thoughtworks.go.server.dao.StageDao; import com.thoughtworks.go.server.domain.Username; import com.thoughtworks.go.server.persistence.MaterialRepository; import com.thoughtworks.go.server.transaction.TransactionTemplate; import com.thoughtworks.go.serverhealth.ServerHealthService; import com.thoughtworks.go.util.GoConfigFileHelper; import com.thoughtworks.go.util.GoConstants; import com.thoughtworks.go.util.TimeProvider; import org.junit.*; 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.TransactionCallback; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import java.io.IOException; import java.util.Date; import static com.thoughtworks.go.helper.ModificationsMother.modifySomeFiles; import static com.thoughtworks.go.server.dao.DatabaseAccessHelper.AGENT_UUID; import static com.thoughtworks.go.util.GoConstants.DEFAULT_APPROVED_BY; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNot.not; import static org.hamcrest.core.IsNull.nullValue; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; @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 BuildRepositoryServiceIntegrationTest { @Autowired private BuildRepositoryService buildRepositoryService; @Autowired private StageService stageService; @Autowired private PipelineService pipelineService; @Autowired private GoConfigDao goConfigDao; @Autowired private ScheduleService scheduleService; @Autowired private ServerHealthService serverHealthService; @Autowired private PipelineDao pipelineDao; @Autowired private JobInstanceDao jobInstanceDao; @Autowired private JobInstanceService jobInstanceService; @Autowired private GoConfigService goConfigService; @Autowired private PipelineScheduleQueue pipelineScheduleQueue; @Autowired private DatabaseAccessHelper dbHelper; @Autowired private MaterialRepository materialRepository; @Autowired private GoCache goCache; @Autowired private TransactionTemplate transactionTemplate; @Autowired private StageDao stageDao; @Autowired private InstanceFactory instanceFactory; private static GoConfigFileHelper config = new GoConfigFileHelper(); private PipelineConfig mingle; private static final String DEV_STAGE = "dev"; private static final String FT_STAGE = "ft"; private Pipeline pipeline; private TestRepo svnTestRepo; private static final String PIPELINE_NAME = "mingle"; public Subversion svnRepo; private static final String HOSTNAME = "10.18.0.1"; private final String md5 = "md5-test"; @AfterClass public static void tearDownConfigFileLocation() throws IOException { TestRepo.internalTearDown(); } @Before public void setUp() throws Exception { dbHelper.onSetUp(); config.onSetUp(); config.usingCruiseConfigDao(goConfigDao); goConfigService.forceNotifyListeners(); svnTestRepo = new SvnTestRepo("testsvnrepo"); svnRepo = new SvnCommand(null, svnTestRepo.projectRepositoryUrl()); config.addPipeline(PIPELINE_NAME, DEV_STAGE, svnRepo, "foo"); mingle = config.addStageToPipeline(PIPELINE_NAME, FT_STAGE, "bar"); config.addAgent(HOSTNAME, AGENT_UUID); pipeline = dbHelper.newPipelineWithAllStagesPassed(mingle); goCache.clear(); } @After public void teardown() throws Exception { dbHelper.onTearDown(); config.onTearDown(); pipelineScheduleQueue.clear(); } @Test public void shouldScheduleNextStageAndPipelineWhenStagePassed() throws Exception { createPipelineWithFirstStageCompletedAndNextStageBuilding(StageState.Passed); Stage stage1 = stageDao.mostRecentWithBuilds(PIPELINE_NAME, mingle.findBy(new CaseInsensitiveString(DEV_STAGE))); assertThat("Should be approved", stage1.isApproved(), is(true)); assertThat(stage1.getApprovedBy(), is(DEFAULT_APPROVED_BY)); Stage stage2 = stageDao.mostRecentWithBuilds(PIPELINE_NAME, mingle.findBy(new CaseInsensitiveString(FT_STAGE))); assertThat(stage2.stageState(), is(StageState.Building)); assertThat(stage2.getApprovedBy(), is(DEFAULT_APPROVED_BY)); assertThat("In same pipeline", stage1.getPipelineId(), is(stage2.getPipelineId())); } @Test public void shouldNotTriggerPipelineWhenCurrentStageFailed() throws Exception { PipelineConfig product = config.addPipeline("product", "dev", svnRepo, "build"); config.setDependencyOn(product, PIPELINE_NAME, DEV_STAGE); int oldSize = pipelineScheduleQueue.toBeScheduled().size(); createPipelineWithFirstStageCompletedAndNextStageBuilding(StageState.Failed); assertThat(pipelineScheduleQueue.toBeScheduled().size(), is(oldSize)); } @Test public void shouldNotSkipBuildingStateWhenTransitionFromPreparingToCompleting() throws Exception { //Bug Fix for #1630 PipelineConfig pipelineConfig = config.addPipeline("product", "dev", svnRepo, "build"); Pipeline pipelineInPreparingState = PipelineMother.preparing(pipelineConfig); dbHelper.savePipelineWithStagesAndMaterials(pipelineInPreparingState); Pipeline reloadedPipeline = pipelineDao.mostRecentPipeline("product"); Stage stage = reloadedPipeline.getFirstStage(); JobInstance job = stage.getJobInstances().first(); String agentUuid = job.getAgentUuid(); long buildId = job.getId(); buildRepositoryService.completing(new JobIdentifier(reloadedPipeline, stage, job), JobResult.Failed, agentUuid); JobInstance reloadedJob = jobInstanceDao.buildByIdWithTransitions(buildId); assertThat(reloadedJob.getTransitions().byState(JobState.Building), is(not(nullValue()))); } @Test @Deprecated public void shouldNotScheduleDuplicatedStage() throws Exception { Pipeline oldPipeline = dbHelper.newPipelineWithFirstStagePassed(mingle); Pipeline latestPipeline = dbHelper.newPipelineWithAllStagesPassed(mingle); int oldSize = latestPipeline.getStages().size(); Stage stage = oldPipeline.getStages().first(); scheduleService.automaticallyTriggerRelevantStagesFollowingCompletionOf(stage); oldPipeline = pipelineDao.loadPipeline(oldPipeline.getId()); Stage ftStage = oldPipeline.getStages().byName(FT_STAGE); JobInstance secondJob = ftStage.getJobInstances().first(); secondJob.setIdentifier(new JobIdentifier(oldPipeline, ftStage, secondJob)); secondJob.setAgentUuid(AGENT_UUID); jobInstanceDao.updateAssignedInfo(secondJob); reportJobPassed(secondJob); latestPipeline = pipelineDao.loadPipeline(latestPipeline.getId()); assertThat(latestPipeline.getStages().size(), is(oldSize)); } @Test public void shouldNotUpdateIgnoredBuildStatus() throws Exception { Stage stage = dbHelper.saveBuildingStage("studios", "dev"); JobInstance job = stage.getJobInstances().get(0); scheduleService.rescheduleJob(job); reportJobPassed(job); JobInstance reloaded = jobInstanceDao.buildByIdWithTransitions(job.getId()); assertThat(reloaded.getState(), is(JobState.Rescheduled)); } @Test public void shouldNotUpdateIgnoredBuildResult() throws Exception { Stage stage = dbHelper.saveBuildingStage("studios", "dev"); JobInstance job = stage.getJobInstances().get(0); scheduleService.rescheduleJob(job); buildRepositoryService.completing(job.getIdentifier(), JobResult.Passed, AGENT_UUID); JobInstance reloaded = jobInstanceDao.buildByIdWithTransitions(job.getId()); assertThat(reloaded.getResult(), is(JobResult.Unknown)); } @Test public void shouldNotScheduleNextStageWhenApprovalRequiredAndStagePassed() throws Exception { config.requireApproval(PIPELINE_NAME, FT_STAGE); Pipeline newPipeline = createPipelineWithFirstStageBuilding(mingle); completeStageAndTrigger(newPipeline.getFirstStage()); Stage stage1 = stageDao.mostRecentWithBuilds(PIPELINE_NAME, mingle.findBy(new CaseInsensitiveString(DEV_STAGE))); assertThat("Should be approved", stage1.isApproved(), is(true)); assertThat("1st stage should be approved by Cruise", stage1.getApprovedBy(), is(GoConstants.DEFAULT_APPROVED_BY)); assertThat("Should be passed", stage1.getJobInstances().first().getResult(), is(JobResult.Passed)); Stage stage2 = stageDao.mostRecentWithBuilds(PIPELINE_NAME, mingle.findBy(new CaseInsensitiveString(FT_STAGE))); assertThat(stage2.getPipelineId(), is(pipeline.getId())); assertThat(stage2.stageState(), is(StageState.Passed)); assertThat("In different pipelines", stage1.getPipelineId(), is(not(stage2.getPipelineId()))); } @Test public void shouldNotScheduleNextStageIfItContainsNoJobs() throws Exception { config.setRunOnAllAgents(PIPELINE_NAME, FT_STAGE, "bar", true); config.addResourcesFor(PIPELINE_NAME, FT_STAGE, "bar", "non-existent"); Pipeline newPipeline = createPipelineWithFirstStageBuilding(mingle); try { completeStageAndTrigger(newPipeline.getFirstStage()); fail("should not have scheduled the stage"); } catch (CannotScheduleException e) { // ok } Stage stage1 = stageDao.mostRecentWithBuilds(PIPELINE_NAME, mingle.findBy(new CaseInsensitiveString(DEV_STAGE))); assertThat(stage1.stageState(), is(StageState.Passed)); Stage stage2 = stageService.findStageWithIdentifier(new StageIdentifier(newPipeline.getIdentifier(), FT_STAGE, "1")); assertThat(stage2, is(instanceOf(NullStage.class))); } @Test public void shouldScheduleCurrentStageInNextEligiblePipeline() throws Exception { Stage oldFtStage = createPipelineWithFirstStageCompletedAndNextStageBuilding(StageState.Passed); createNewPipelineWithFirstStagePassed(); Stage mostRecentPassedStage = createNewPipelineWithFirstStagePassed(); createNewPipelineWithFirstStageFailed(); dbHelper.passStage(oldFtStage); reportJobPassed(oldFtStage.getJobInstances().get(0)); Stage mingleFt = stageDao.mostRecentWithBuilds(PIPELINE_NAME, mingle.findBy(new CaseInsensitiveString(FT_STAGE))); assertThat(mingleFt.getPipelineId(), is(mostRecentPassedStage.getPipelineId())); } @Test public void shouldUpdateStageStatusWhenAllJobsPass() throws Exception { Stage stage1 = createPipelineWithFirstStageCompletedAndNextStageBuilding(StageState.Passed); assertThat(stage1.getResult(), is(StageResult.Unknown)); JobInstances jobs = stage1.getJobInstances(); for (JobInstance job : jobs) { buildRepositoryService.completing(job.getIdentifier(), JobResult.Passed, AGENT_UUID); reportJobPassed(job); } Stage after = stageService.stageById(stage1.getId()); JobInstances instances = after.getJobInstances(); assertThat(instances.size(), is(1)); assertThat(instances.get(0).getResult(), is(JobResult.Passed)); assertThat(instances.get(0).getState(), is(JobState.Completed)); assertThat(after.getResult(), is(StageResult.Passed)); } @Test public void shouldNotScheduleCurrentStageIfAlreadyMostRecentPipeline() throws Exception { Stage mostRecent = createPipelineWithFirstStageCompletedAndNextStageBuilding(StageState.Passed); reportJobPassed(mostRecent.getJobInstances().get(0)); Stage mingleFt = stageDao.mostRecentWithBuilds(PIPELINE_NAME, mingle.findBy(new CaseInsensitiveString(FT_STAGE))); assertThat(mingleFt.getPipelineId(), is(mostRecent.getPipelineId())); } @Test public void shouldNotScheduleNextStageWhenStageAlreadyActive() throws Exception { createPipelineWithFirstStageCompletedAndNextStageBuilding(StageState.Passed); Stage originalFt = stageDao.mostRecentWithBuilds(PIPELINE_NAME, mingle.findBy(new CaseInsensitiveString(FT_STAGE))); createPipelineWithFirstStageCompletedAndNextStageBuilding(StageState.Passed); Stage mingleFt = stageDao.mostRecentWithBuilds(PIPELINE_NAME, mingle.findBy(new CaseInsensitiveString(FT_STAGE))); assertThat(mingleFt.getId(), is(originalFt.getId())); } @Test public void shouldTriggerCurrentStageInNextEligiblePipelineIfPrevStageIsAutoApproval() throws Exception { Stage oldFtStage = createPipelineWithFirstStageCompletedAndNextStageBuilding(StageState.Passed); Pipeline newPipeline = createPipelineWithFirstStageCompleted(mingle); completeStageAndTrigger(oldFtStage); Stage mostRecent = stageDao.mostRecentWithBuilds(PIPELINE_NAME, mingle.findBy(new CaseInsensitiveString(FT_STAGE))); assertThat(mostRecent.getPipelineId(), is(oldFtStage.getPipelineId() + 1)); } @Test public void shouldNotTriggerCurrentStageInAnyNewerPipelineIfCurrentStageIsManualApproval() throws Exception { config.configureStageAsManualApproval(PIPELINE_NAME, FT_STAGE); Stage oldFtStage = createPipelineWithFirstStageCompletedAndNextStageBuilding(StageState.Passed); Pipeline newPipeline = createPipelineWithFirstStageCompleted(mingle); Stage mostRecent = stageDao.mostRecentWithBuilds(PIPELINE_NAME, mingle.findBy(new CaseInsensitiveString(FT_STAGE))); assertThat(mostRecent.getId(), is(oldFtStage.getId())); } private JobInstance completeStageAndTrigger(Stage oldFtStage) throws Exception { JobInstance job = oldFtStage.getJobInstances().first(); buildRepositoryService.completing(job.getIdentifier(), JobResult.Passed, AGENT_UUID); reportJobPassed(job); return jobInstanceService.buildByIdWithTransitions(job.getId()); } @Test public void shouldCancelAllJobs() throws Exception { mingle = PipelineMother.twoBuildPlansWithResourcesAndSvnMaterialsAtUrl(PIPELINE_NAME, DEV_STAGE, svnTestRepo.projectRepositoryUrl()); StageConfig devStage = mingle.get(0); schedulePipeline(mingle); final Stage stage = stageDao.mostRecentWithBuilds(CaseInsensitiveString.str(mingle.name()), devStage); transactionTemplate.execute(new TransactionCallbackWithoutResult() { public void doInTransactionWithoutResult(TransactionStatus status) { stageService.cancelStage(stage); } }); final Stage cancelled = stageService.stageById(stage.getId()); assertThat(cancelled.stageState(), is(StageState.Cancelled)); final JobInstances builds = cancelled.getJobInstances(); for (JobInstance job : builds) { assertThat(job.currentStatus(), is(JobState.Completed)); assertThat(job.getResult(), is(JobResult.Cancelled)); assertThat(job.displayStatusWithResult(), is("cancelled")); } } private Stage createPipelineWithFirstStageCompletedAndNextStageBuilding(StageState stageState) throws Exception { Pipeline newPipeline = createPipelineWithFirstStageBuilding(mingle); Stage mostRecent = newPipeline.getFirstStage(); if (stageState.equals(StageState.Failed)) { dbHelper.failStage(mostRecent); } else { dbHelper.passStage(mostRecent); } reportJobPassed(mostRecent.getJobInstances().first()); Stage nextStage = stageDao.mostRecentWithBuilds(PIPELINE_NAME, mingle.findBy(new CaseInsensitiveString(FT_STAGE))); dbHelper.buildingBuildInstance(nextStage); return nextStage; } private void reportJobPassed(JobInstance jobInstance) throws Exception { buildRepositoryService.updateStatusFromAgent(jobInstance.getIdentifier(), JobState.Completed, AGENT_UUID); } private void reportJobCompleting(JobInstance jobInstance) { buildRepositoryService.completing(jobInstance.getIdentifier(), JobResult.Passed, AGENT_UUID); } private Pipeline createPipelineWithFirstStageCompleted(PipelineConfig pipeline) throws Exception { Stage firstStage = createPipelineWithFirstStageBuilding(pipeline).getFirstStage(); reportJobCompleting(firstStage.getJobInstances().first()); reportJobPassed(firstStage.getJobInstances().first()); return pipelineDao.mostRecentPipeline(CaseInsensitiveString.str(pipeline.name())); } private Pipeline createPipelineWithFirstStageBuilding(PipelineConfig pipeline) throws StageAlreadyBuildingException { Pipeline scheduledPipeline = schedulePipeline(pipeline); dbHelper.saveBuildingStage(scheduledPipeline.getFirstStage()); return pipelineDao.mostRecentPipeline(CaseInsensitiveString.str(pipeline.name())); } private Pipeline schedulePipeline(final PipelineConfig pipeline) { return (Pipeline) transactionTemplate.execute(new TransactionCallback() { public Object doInTransaction(TransactionStatus status) { MaterialRevisions materialRevisions = new MaterialRevisions(); for (Material material : new MaterialConfigConverter().toMaterials(pipeline.materialConfigs())) { materialRevisions.addRevision(material, new Modification("user", "comment", null, new Date(), ModificationsMother.nextRevision())); } materialRepository.save(materialRevisions); Pipeline scheduledPipeline = instanceFactory.createPipelineInstance(pipeline, BuildCause.createManualForced(materialRevisions, Username.ANONYMOUS), new DefaultSchedulingContext(DEFAULT_APPROVED_BY), md5, new TimeProvider()); pipelineService.save(scheduledPipeline); return scheduledPipeline; } }); } private Stage createNewPipelineWithFirstStageFailed() throws Exception { return (Stage) transactionTemplate.execute(new TransactionCallback() { public Object doInTransaction(TransactionStatus status) { Pipeline forcedPipeline = instanceFactory.createPipelineInstance(mingle, modifySomeFiles(mingle), new DefaultSchedulingContext(DEFAULT_APPROVED_BY), md5, new TimeProvider()); materialRepository.save(forcedPipeline.getBuildCause().getMaterialRevisions()); pipelineService.save(forcedPipeline); Stage stage = forcedPipeline.getFirstStage(); dbHelper.failStage(stage); return stage; } }); } private Stage createNewPipelineWithFirstStagePassed() throws Exception { Pipeline forcedPipeline = schedulePipeline(mingle); dbHelper.passStage(forcedPipeline.getFirstStage()); return forcedPipeline.getFirstStage(); } }