/*
* 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 java.util.ArrayList;
import java.util.List;
import com.thoughtworks.go.config.GoConfigDao;
import com.thoughtworks.go.config.EnvironmentVariablesConfig;
import com.thoughtworks.go.domain.JobInstances;
import com.thoughtworks.go.domain.JobResult;
import com.thoughtworks.go.domain.JobState;
import com.thoughtworks.go.domain.Pipeline;
import com.thoughtworks.go.domain.Stage;
import com.thoughtworks.go.domain.StageConfigIdentifier;
import com.thoughtworks.go.fixture.PipelineWithMultipleStages;
import com.thoughtworks.go.server.dao.DatabaseAccessHelper;
import com.thoughtworks.go.server.dao.StageDao;
import com.thoughtworks.go.server.persistence.MaterialRepository;
import com.thoughtworks.go.server.service.result.HttpOperationResult;
import com.thoughtworks.go.server.transaction.TransactionTemplate;
import com.thoughtworks.go.util.GoConfigFileHelper;
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.security.GrantedAuthority;
import org.springframework.security.context.SecurityContext;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.userdetails.User;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static com.thoughtworks.go.util.DataStructureUtils.a;
import static org.hamcrest.Matchers.containsString;
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;
@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 ScheduleStageTest {
@Autowired private ScheduleService scheduleService;
@Autowired private DatabaseAccessHelper dbHelper;
@Autowired private GoConfigDao dao;
@Autowired private StageDao stageDao;
@Autowired private MaterialRepository materialRepository;
@Autowired private TransactionTemplate transactionTemplate;
private PipelineWithMultipleStages fixture;
private GoConfigFileHelper configHelper;
@Before
public void setUp() throws Exception {
configHelper = new GoConfigFileHelper().usingCruiseConfigDao(dao);
fixture = new PipelineWithMultipleStages(3, materialRepository, transactionTemplate);
fixture.usingThreeJobs();
fixture.usingConfigHelper(configHelper).usingDbHelper(dbHelper).onSetUp();
}
@After
public void tearDown() throws Exception {
fixture.onTearDown();
}
@Test
public void shouldRerunStageUsingPipelineCounter() throws Exception {
Pipeline pipeline = fixture.createdPipelineWithAllStagesPassed();
Stage oldStage = pipeline.getStages().byName(fixture.devStage);
scheduleService.rerunStage(pipeline.getName(), String.valueOf(pipeline.getCounter()), fixture.devStage);
Stage stage = dbHelper.getStageDao().mostRecentStage(
new StageConfigIdentifier(pipeline.getName(), fixture.devStage));
assertThat(stage.getCounter(), is(oldStage.getCounter() + 1));
}
@Test
public void shouldResolveEnvironmentVariablesForStateReRun() throws Exception {
Pipeline pipeline = fixture.createdPipelineWithAllStagesPassed();
EnvironmentVariablesConfig pipelineVariables = new EnvironmentVariablesConfig();
pipelineVariables.add("pipelineEnv", "pipelineFoo");
pipelineVariables.add("stageEnv", "pipelineBar");
pipelineVariables.add("jobEnv", "pipelineBaz");
configHelper.addEnvironmentVariableToPipeline(fixture.pipelineName, pipelineVariables);
EnvironmentVariablesConfig stageVariables = new EnvironmentVariablesConfig();
stageVariables.add("stageEnv", "stageBar");
stageVariables.add("jobEnv", "stageBaz");
configHelper.addEnvironmentVariableToStage(fixture.pipelineName, fixture.devStage, stageVariables);
EnvironmentVariablesConfig jobVariables = new EnvironmentVariablesConfig();
jobVariables.add("jobEnv", "jobBaz");
configHelper.addEnvironmentVariableToJob(fixture.pipelineName, fixture.devStage, fixture.JOB_FOR_DEV_STAGE, jobVariables);
Stage stage = scheduleService.rerunStage(pipeline.getName(), String.valueOf(pipeline.getCounter()), fixture.devStage);
dbHelper.passStage(stage);
EnvironmentVariablesConfig expectedVariableOrder = new EnvironmentVariablesConfig();
expectedVariableOrder.add("pipelineEnv", "pipelineFoo");
expectedVariableOrder.add("stageEnv", "stageBar");
expectedVariableOrder.add("jobEnv", "jobBaz");
JobInstances jobInstances = stage.getJobInstances();
assertThat(jobInstances.getByName(fixture.JOB_FOR_DEV_STAGE).getPlan().getVariables(), is(expectedVariableOrder));
}
@Test
public void shouldResolveEnvironmentVariablesForJobReRun() throws Exception {
Pipeline pipeline = fixture.createdPipelineWithAllStagesPassed();
Stage oldStage = stageDao.stageByIdWithBuilds(pipeline.getStages().byName(fixture.devStage).getId());
EnvironmentVariablesConfig pipelineVariables = new EnvironmentVariablesConfig();
pipelineVariables.add("pipelineEnv", "pipelineFoo");
pipelineVariables.add("stageEnv", "pipelineBar");
pipelineVariables.add("jobEnv", "pipelineBaz");
configHelper.addEnvironmentVariableToPipeline(fixture.pipelineName, pipelineVariables);
EnvironmentVariablesConfig stageVariables = new EnvironmentVariablesConfig();
stageVariables.add("stageEnv", "stageBar");
stageVariables.add("jobEnv", "stageBaz");
configHelper.addEnvironmentVariableToStage(fixture.pipelineName, fixture.devStage, stageVariables);
EnvironmentVariablesConfig jobVariables = new EnvironmentVariablesConfig();
jobVariables.add("jobEnv", "jobBaz");
configHelper.addEnvironmentVariableToJob(fixture.pipelineName, fixture.devStage, fixture.JOB_FOR_DEV_STAGE, jobVariables);
Stage stage = scheduleService.rerunJobs(oldStage, a(fixture.JOB_FOR_DEV_STAGE), new HttpOperationResult());
EnvironmentVariablesConfig expectedVariableOrder = new EnvironmentVariablesConfig();
expectedVariableOrder.add("pipelineEnv", "pipelineFoo");
expectedVariableOrder.add("stageEnv", "stageBar");
expectedVariableOrder.add("jobEnv", "jobBaz");
JobInstances jobInstances = stage.getJobInstances();
assertThat(jobInstances.getByName(fixture.JOB_FOR_DEV_STAGE).getPlan().getVariables(), is(expectedVariableOrder));
}
@Test
public void shouldRerunOnlyGivenJobsFromExistingStage() throws Exception {
Pipeline pipeline = fixture.createdPipelineWithAllStagesPassed();
Stage stage = scheduleService.rerunStage(pipeline.getName(), String.valueOf(pipeline.getCounter()), fixture.devStage);
dbHelper.passStage(stage);
assertThat(stage.hasRerunJobs(), is(false));
Stage oldStage = stageDao.stageByIdWithBuilds(pipeline.getStages().byName(fixture.devStage).getId());
assertThat(oldStage.hasRerunJobs(), is(false));
HttpOperationResult result = new HttpOperationResult();
Stage newStage = scheduleService.rerunJobs(oldStage, a("foo", "foo3"), result);
Stage loadedLatestStage = dbHelper.getStageDao().findStageWithIdentifier(newStage.getIdentifier());
assertThat(loadedLatestStage.isLatestRun(), is(true));
assertThat(loadedLatestStage.getJobInstances().getByName("foo").getResult(), is(JobResult.Unknown));
assertThat(loadedLatestStage.getJobInstances().getByName("foo").getState(), is(JobState.Scheduled));
assertThat(loadedLatestStage.getJobInstances().getByName("foo").getOriginalJobId(), is(nullValue()));
assertThat(loadedLatestStage.getJobInstances().getByName("foo").isRerun(), is(true));
assertThat(loadedLatestStage.getJobInstances().getByName("foo").isCopy(), is(false));
assertThat(loadedLatestStage.getJobInstances().getByName("foo2").getResult(), is(JobResult.Passed));
assertThat(loadedLatestStage.getJobInstances().getByName("foo2").getState(), is(JobState.Completed));
assertThat(loadedLatestStage.getJobInstances().getByName("foo2").getOriginalJobId(), is(oldStage.getJobInstances().getByName("foo2").getId()));
assertThat(loadedLatestStage.getJobInstances().getByName("foo2").isRerun(), is(false));
assertThat(loadedLatestStage.getJobInstances().getByName("foo2").isCopy(), is(true));
assertThat(loadedLatestStage.getJobInstances().getByName("foo3").getResult(), is(JobResult.Unknown));
assertThat(loadedLatestStage.getJobInstances().getByName("foo3").getState(), is(JobState.Scheduled));
assertThat(loadedLatestStage.getJobInstances().getByName("foo3").getOriginalJobId(), is(nullValue()));
assertThat(loadedLatestStage.getJobInstances().getByName("foo3").isRerun(), is(true));
assertThat(loadedLatestStage.getJobInstances().getByName("foo3").isCopy(), is(false));
assertThat(loadedLatestStage, is(newStage));
assertThat(loadedLatestStage.hasRerunJobs(), is(true));
assertThat(loadedLatestStage.getCounter(), is(oldStage.getCounter() + 2));
assertThat(loadedLatestStage.getIdentifier().getPipelineCounter(), is(oldStage.getIdentifier().getPipelineCounter()));
assertThat(result.canContinue(), is(true));
}
@Test
public void shouldConsiderCopyOfRerunJobACopyAndNotRerun() throws Exception {
Pipeline pipeline = fixture.createdPipelineWithAllStagesPassed();
Stage stage = scheduleService.rerunStage(pipeline.getName(), String.valueOf(pipeline.getCounter()), fixture.devStage);
assertThat(stage.hasRerunJobs(), is(false));
dbHelper.passStage(stage);
Stage oldStage = stageDao.stageByIdWithBuilds(pipeline.getStages().byName(fixture.devStage).getId());
assertThat(oldStage.hasRerunJobs(), is(false));
HttpOperationResult result = new HttpOperationResult();
Stage newStage = scheduleService.rerunJobs(oldStage, a("foo", "foo3"), result);
Stage loadedLatestStage = dbHelper.getStageDao().findStageWithIdentifier(newStage.getIdentifier());
assertThat(loadedLatestStage.hasRerunJobs(), is(true));
dbHelper.passStage(loadedLatestStage);
JobInstances jobsBeforeLatestRerunJobs = loadedLatestStage.getJobInstances();
newStage = scheduleService.rerunJobs(loadedLatestStage, a("foo2"), result);
loadedLatestStage = dbHelper.getStageDao().findStageWithIdentifier(newStage.getIdentifier());
assertThat(loadedLatestStage.getJobInstances().getByName("foo").getResult(), is(JobResult.Passed));
assertThat(loadedLatestStage.getJobInstances().getByName("foo").getState(), is(JobState.Completed));
assertThat(loadedLatestStage.getJobInstances().getByName("foo").getOriginalJobId(), is(jobsBeforeLatestRerunJobs.getByName("foo").getId()));
assertThat(loadedLatestStage.getJobInstances().getByName("foo").isRerun(), is(false));
assertThat(loadedLatestStage.getJobInstances().getByName("foo").isCopy(), is(true));
assertThat(loadedLatestStage.getJobInstances().getByName("foo2").getResult(), is(JobResult.Unknown));
assertThat(loadedLatestStage.getJobInstances().getByName("foo2").getState(), is(JobState.Scheduled));
assertThat(loadedLatestStage.getJobInstances().getByName("foo2").getOriginalJobId(), is(nullValue()));
assertThat(loadedLatestStage.getJobInstances().getByName("foo2").isRerun(), is(true));
assertThat(loadedLatestStage.getJobInstances().getByName("foo2").isCopy(), is(false));
assertThat(loadedLatestStage.getJobInstances().getByName("foo3").getResult(), is(JobResult.Passed));
assertThat(loadedLatestStage.getJobInstances().getByName("foo3").getState(), is(JobState.Completed));
assertThat(loadedLatestStage.getJobInstances().getByName("foo3").getOriginalJobId(), is(jobsBeforeLatestRerunJobs.getByName("foo3").getId()));
assertThat(loadedLatestStage.getJobInstances().getByName("foo3").isRerun(), is(false));
assertThat(loadedLatestStage.getJobInstances().getByName("foo3").isCopy(), is(true));
assertThat(loadedLatestStage, is(newStage));
assertThat(loadedLatestStage.hasRerunJobs(), is(true));
assertThat(loadedLatestStage.getCounter(), is(oldStage.getCounter() + 3));
assertThat(loadedLatestStage.getIdentifier().getPipelineCounter(), is(oldStage.getIdentifier().getPipelineCounter()));
assertThat(result.canContinue(), is(true));
}
@Test
public void shouldFailRerunWhenJobConfigDoesNotExist() throws Exception {
Pipeline pipeline = fixture.createPipelineWithFirstStageScheduled();
Stage oldStage = pipeline.getStages().byName(fixture.devStage);
dbHelper.pass(pipeline);
configHelper.removeJob(pipeline.getName(), fixture.devStage, "foo3");
HttpOperationResult result = new HttpOperationResult();
Stage newStage = scheduleService.rerunJobs(oldStage, a("foo3"), result);
assertThat(result.canContinue(), is(false));
assertThat(result.message(), containsString("Cannot rerun job 'foo3'. Configuration for job doesn't exist."));
assertThat(newStage, is(nullValue()));
}
@Test
public void shouldRerunJobsWithUserAsApprover() throws Exception {
Pipeline pipeline = fixture.createdPipelineWithAllStagesPassed();
Stage oldStage = pipeline.getStages().byName(fixture.devStage);
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(new UsernamePasswordAuthenticationToken(new User("loser", "pass", true, true, true, true, new GrantedAuthority[]{}), null));
HttpOperationResult result = new HttpOperationResult();
Stage newStage = scheduleService.rerunJobs(oldStage, a("foo", "foo3"), result);
Stage loadedLatestStage = dbHelper.getStageDao().findStageWithIdentifier(newStage.getIdentifier());
assertThat(loadedLatestStage.getApprovedBy(), is("loser"));
assertThat(oldStage.getApprovedBy(), is(not("loser")));
assertThat(result.canContinue(), is(true));
}
@Test
public void shouldFailWhenStageAlreadyActive() throws Exception {
Pipeline pipeline = fixture.createPipelineWithFirstStageScheduled();
Stage oldStage = pipeline.getStages().byName(fixture.devStage);
HttpOperationResult result = new HttpOperationResult();
Stage newStage = scheduleService.rerunJobs(oldStage, a("foo", "foo3"), result);
assertThat(result.canContinue(), is(false));
assertThat(result.message(), containsString("Pipeline[name='" + pipeline.getName() + "', counter='" + pipeline.getCounter() + "', label='" + pipeline.getLabel() + "'] is still in progress"));
assertThat(result.message(), containsString("Cannot schedule"));
assertThat(newStage, is(nullValue()));
}
@Test
public void shouldNotRunStageIfItsPreviousStageHasNotBeenRun() throws Exception {
Pipeline pipeline = fixture.createPipelineWithFirstStagePassedAndSecondStageHasNotStarted();
String theThirdStage = fixture.stageName(3);
try {
scheduleService.rerunStage(pipeline.getName(), pipeline.getLabel(), theThirdStage);
} catch (Exception e) {
assertThat(e.getMessage(), is(String.format(
"Can not run stage [%s] in pipeline [%s] because its previous stage has not been run.",
theThirdStage, pipeline.getName())));
}
}
@Test
public void shouldNotScheduleAStageIfAnyStageForThatPipelineIsAlreadyRunning() throws Exception {
fixture.createdPipelineWithAllStagesPassed();
final List<Exception> exceptions = new ArrayList<>();
Thread t1 = new Thread(rerunStage(exceptions, fixture.devStage));
Thread t2 = new Thread(rerunStage(exceptions, fixture.ftStage));
t1.start();
t2.start();
t1.join();
t2.join();
assertThat(exceptions.size(), is(1));
}
private Runnable rerunStage(final List<Exception> exceptions, final String ftStage) {
return new Runnable() {
public void run() {
try {
scheduleService.rerunStage(fixture.pipelineName, fixture.pipelineLabel(), ftStage);
} catch (Exception e) {
exceptions.add(e);
}
}
};
}
}