/*
* 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.dao;
import com.thoughtworks.go.config.BasicCruiseConfig;
import com.thoughtworks.go.config.CaseInsensitiveString;
import com.thoughtworks.go.config.CruiseConfig;
import com.thoughtworks.go.config.GoConfigDao;
import com.thoughtworks.go.config.materials.dependency.DependencyMaterial;
import com.thoughtworks.go.config.materials.git.GitMaterial;
import com.thoughtworks.go.database.Database;
import com.thoughtworks.go.domain.*;
import com.thoughtworks.go.domain.buildcause.BuildCause;
import com.thoughtworks.go.domain.materials.Modification;
import com.thoughtworks.go.domain.materials.git.GitMaterialInstance;
import com.thoughtworks.go.helper.*;
import com.thoughtworks.go.presentation.pipelinehistory.*;
import com.thoughtworks.go.server.cache.GoCache;
import com.thoughtworks.go.server.persistence.MaterialRepository;
import com.thoughtworks.go.server.service.StubGoCache;
import com.thoughtworks.go.server.transaction.TestTransactionSynchronizationManager;
import com.thoughtworks.go.server.transaction.TransactionSynchronizationManager;
import com.thoughtworks.go.server.transaction.TransactionTemplate;
import com.thoughtworks.go.util.TestUtils;
import com.thoughtworks.go.util.TimeProvider;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.hamcrest.Matchers;
import org.hibernate.SessionFactory;
import org.hibernate.classic.Session;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.orm.ibatis.SqlMapClientTemplate;
import org.springframework.transaction.support.SimpleTransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import java.util.*;
import static com.thoughtworks.go.util.DataStructureUtils.m;
import static com.thoughtworks.go.util.IBatisUtil.arguments;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
public class PipelineSqlMapDaoCachingTest {
private GoCache goCache;
private PipelineSqlMapDao pipelineDao;
private TransactionTemplate transactionTemplate;
private TransactionSynchronizationManager transactionSynchronizationManager;
private GoConfigDao configFileDao;
private org.hibernate.SessionFactory mockSessionFactory;
private SqlMapClientTemplate mockTemplate;
private EnvironmentVariableDao environmentVariableDao;
private MaterialRepository repository;
private Session session;
@Before
public void setup() throws Exception {
transactionSynchronizationManager = mock(TransactionSynchronizationManager.class);
goCache = new StubGoCache(new TestTransactionSynchronizationManager());
goCache.clear();
mockTemplate = mock(SqlMapClientTemplate.class);
repository = mock(MaterialRepository.class);
environmentVariableDao = mock(EnvironmentVariableDao.class);
mockSessionFactory = mock(SessionFactory.class);
repository = mock(MaterialRepository.class);
transactionTemplate = mock(TransactionTemplate.class);
configFileDao = mock(GoConfigDao.class);
pipelineDao = new PipelineSqlMapDao(null, repository, goCache, environmentVariableDao, transactionTemplate, null,
transactionSynchronizationManager, null, configFileDao, mock(Database.class), mockSessionFactory);
pipelineDao.setSqlMapClientTemplate(mockTemplate);
session = mock(Session.class);
when(mockSessionFactory.getCurrentSession()).thenReturn(session);
when(configFileDao.load()).thenReturn(GoConfigMother.defaultCruiseConfig());
}
@Test
public void buildCauseForPipelineIdentifiedByNameAndCounter_shouldUseCacheAndBeCaseInsensitive() throws Exception {
Pipeline expected = PipelineMother.pipeline("pipeline");
expected.setId(3);
MaterialRevisions originalMaterialRevisions = ModificationsMother.createHgMaterialRevisions();
when(repository.findMaterialRevisionsForPipeline(3)).thenReturn(originalMaterialRevisions);
when(mockTemplate.queryForObject("findPipelineByNameAndCounter", m("name", "pipeline", "counter", 15))).thenReturn(expected);
BuildCause buildCause = pipelineDao.findBuildCauseOfPipelineByNameAndCounter("pipeline", 15);
verify(mockTemplate).queryForObject("findPipelineByNameAndCounter", m("name", "pipeline", "counter", 15));
verify(repository).findMaterialRevisionsForPipeline(3);
assertThat(buildCause.getMaterialRevisions(), is(originalMaterialRevisions));
buildCause = pipelineDao.findBuildCauseOfPipelineByNameAndCounter("pipeline".toUpperCase(), 15);
assertThat(buildCause.getMaterialRevisions(), is(originalMaterialRevisions));
verifyNoMoreInteractions(mockTemplate, repository);
}
@Test
public void pipelineWithModsByStageId_shouldCachePipelineWithMods() throws Exception {
Pipeline expected = PipelineMother.pipeline("pipeline");
expected.setId(1);
when(mockTemplate.queryForObject("getPipelineByStageId", 1L)).thenReturn(expected);
Pipeline actual = pipelineDao.pipelineWithModsByStageId("pipeline", 1L);
assertSame(expected, actual);
pipelineDao.pipelineWithModsByStageId("pipeline".toUpperCase(), 1L);
verify(mockTemplate, times(1)).queryForObject("getPipelineByStageId", 1L);
}
@Test
public void findLastSuccessfulStageIdentifier_shouldCacheResult() {
StageIdentifier expected = new StageIdentifier("pipeline", 0, "${COUNT}", "stage", "1");
when(mockTemplate.queryForObject(eq("getLastSuccessfulStageInPipeline"), any())).thenReturn(PipelineMother.passedPipelineInstance("pipeline", "stage", "job"));
pipelineDao.findLastSuccessfulStageIdentifier("pipeline", "stage");
StageIdentifier actual = pipelineDao.findLastSuccessfulStageIdentifier("pipeline", "stage");
assertEquals(actual, expected);
verify(mockTemplate, times(1)).queryForObject(eq("getLastSuccessfulStageInPipeline"), any());
}
@Test
public void findPipelineIds_shouldCacheResultWhenOnlyLatestPipelineIdIsRequested() {
List<Long> expectedIds = new ArrayList<>();
expectedIds.add(1L);
when(mockTemplate.queryForList(eq("getPipelineRange"), any())).thenReturn(expectedIds);
pipelineDao.findPipelineIds("pipelineName", 1, 0);
List<Long> actual = pipelineDao.findPipelineIds("pipelineName", 1, 0);
assertThat(actual.size(), is(1));
assertEquals(expectedIds.get(0), actual.get(0));
verify(mockTemplate, times(1)).queryForList(eq("getPipelineRange"), any());
}
@Test
public void findPipelineIds_shouldNotCacheResultWhenMultiplePipelineIdsOrPipelineIdsFromASubsequentPageAreRequested() {
List<Long> expectedIds = new ArrayList<>();
expectedIds.add(1L);
expectedIds.add(2L);
when(mockTemplate.queryForList(eq("getPipelineRange"), any())).thenReturn(expectedIds);
pipelineDao.findPipelineIds("pipelineName", 2, 0);
pipelineDao.findPipelineIds("pipelineName", 2, 0);
pipelineDao.findPipelineIds("pipelineName", 1, 2);
pipelineDao.findPipelineIds("pipelineName", 1, 2);
verify(mockTemplate, times(4)).queryForList(eq("getPipelineRange"), any());
}
@Test
public void savePipeline_shouldClearLatestPipelineIdCacheCaseInsensitively() {
when(mockTemplate.queryForList(eq("getPipelineRange"), any())).thenReturn(Arrays.asList(99L));
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
((TransactionSynchronizationAdapter) invocation.getArguments()[0]).afterCommit();
return null;
}
}).when(transactionSynchronizationManager).registerSynchronization(any(TransactionSynchronization.class));
when(transactionTemplate.execute(any(TransactionCallback.class))).then(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
((TransactionCallback) invocation.getArguments()[0]).doInTransaction(new SimpleTransactionStatus());
return null;
}
});
pipelineDao.save(PipelineMother.pipeline("pipelineName"));
pipelineDao.findPipelineIds("pipelineName", 1, 0);
pipelineDao.save(PipelineMother.pipeline("pipelineName".toUpperCase()));
pipelineDao.findPipelineIds("pipelineName", 1, 0);
verify(mockTemplate, times(2)).queryForList(eq("getPipelineRange"), any());
}
@Test
public void findLastSuccessfulStageIdentifier_shouldEnsureOnlyOneThreadCanUpdateCacheAtATime() throws Exception {
Pipeline pipelineWithOlderStage = PipelineMother.passedPipelineInstance("pipeline-name", "stage", "job");
Stage stage = pipelineWithOlderStage.findStage("stage");
stage.setIdentifier(new StageIdentifier("pipeline-name", 1, "stage", "1"));
stage.setPipelineId(1L);
when(mockTemplate.queryForObject(eq("getLastSuccessfulStageInPipeline"), any())).thenReturn(pipelineWithOlderStage);
pipelineDao = new PipelineSqlMapDao(null, null, goCache, null, null, null, transactionSynchronizationManager, null, configFileDao, null, mock(SessionFactory.class));
pipelineDao.setSqlMapClientTemplate(mockTemplate);
final Stage newerStage = StageMother.passedStageInstance("stage", "job", "pipeline-name");
newerStage.setPipelineId(1L);
final StageIdentifier newerIdentifer = new StageIdentifier("pipeline-name", 1, "stage", "999999");
newerStage.setIdentifier(newerIdentifer);
new Thread(new Runnable() {
public void run() {
pipelineDao.stageStatusChanged(newerStage);
}
}).start();
TestUtils.sleepQuietly(200);
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
final Pipeline pipeline = PipelineMother.pipeline("mingle");
pipeline.setCounter(i + 1);
Thread thread = new Thread(new Runnable() {
public void run() {
assertEquals(newerIdentifer, pipelineDao.findLastSuccessfulStageIdentifier("pipeline-name", "stage"));
}
}, "thread-" + i);
threads.add(thread);
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
verify(mockTemplate, never()).queryForObject(eq("getLastSuccessfulStageInPipeline"), any());
}
@Test
public void stageStateChanged_shouldUpdateLatestSuccessfulStageIdentifierCache() {
Stage passed = StageMother.passedStageInstance("dev", "job", "pipeline-name");
passed.setPipelineId(1L);
pipelineDao.stageStatusChanged(passed);
StageIdentifier actual = pipelineDao.findLastSuccessfulStageIdentifier("pipeline-name", "dev");
assertEquals(passed.getIdentifier(), actual);
verify(mockTemplate, never()).queryForObject(eq("getLastSuccessfulStageInPipeline"), any());
}
@Test
public void stageStateChanged_shouldNotUpdateLatestSuccessfulStageIdentifierCacheIfStageIsNotPassed() {
Stage passed = StageMother.createPassedStage("pipeline", 1, "stage", 8, "job", new Date());
passed.setPipelineId(1L);
pipelineDao.stageStatusChanged(passed);
pipelineDao.findLastSuccessfulStageIdentifier("pipeline", "stage");
Stage failed = StageMother.completedFailedStageInstance("pipeline", "dev", "job");
failed.setIdentifier(new StageIdentifier("pipeline", 1, "LABEL-1", "stage", "10000"));
failed.setPipelineId(1L);
pipelineDao.stageStatusChanged(failed);
StageIdentifier actual = pipelineDao.findLastSuccessfulStageIdentifier("pipeline", "stage");
assertEquals(passed.getIdentifier(), actual);
verify(mockTemplate, never()).queryForObject(eq("getLastSuccessfulStageInPipeline"), any());
}
@Test
public void loadHistory_shouldCacheResult() {
PipelineInstanceModel pipeline = new PipelineInstanceModel("pipeline", -2, "label", BuildCause.createManualForced(), new StageInstanceModels());
when(mockTemplate.queryForObject(eq("getPipelineHistoryById"), any())).thenReturn(pipeline);
PipelineInstanceModel loaded;
loaded = pipelineDao.loadHistory(99);
loaded = pipelineDao.loadHistory(99);
assertTrue(EqualsBuilder.reflectionEquals(loaded, pipeline));
verify(mockTemplate, times(1)).queryForObject(eq("getPipelineHistoryById"), any());
}
@Test
public void loadHistory_shouldCloneResultSoThatModificationsDoNotAffectTheCachedObjects() {
PipelineInstanceModel pipeline = new PipelineInstanceModel("pipeline", -2, "label", BuildCause.createManualForced(), new StageInstanceModels());
when(mockTemplate.queryForObject(eq("getPipelineHistoryById"), any())).thenReturn(pipeline);
PipelineInstanceModel loaded;
loaded = pipelineDao.loadHistory(99);
loaded = pipelineDao.loadHistory(99);
assertNotSame(pipeline, loaded);
assertTrue(ToStringBuilder.reflectionToString(loaded) + " not equal to\n" + ToStringBuilder.reflectionToString(pipeline),
EqualsBuilder.reflectionEquals(loaded, pipeline));
verify(mockTemplate, times(1)).queryForObject(eq("getPipelineHistoryById"), any());
}
@Test
public void loadActivePipelines_shouldCacheResult() {
final String pipelineName = "pipeline";
CruiseConfig mockCruiseConfig=mock(BasicCruiseConfig.class);
GoConfigDao mockconfigFileDao = mock(GoConfigDao.class);
when(mockconfigFileDao.load()).thenReturn(mockCruiseConfig);
when(mockCruiseConfig.getAllPipelineNames()).thenReturn(Arrays.asList(new CaseInsensitiveString(pipelineName)));
//need to mock configfileDao for this test
pipelineDao = new PipelineSqlMapDao(null, repository, goCache, environmentVariableDao, transactionTemplate, null,
transactionSynchronizationManager, null, mockconfigFileDao, null, mock(SessionFactory.class));
pipelineDao.setSqlMapClientTemplate(mockTemplate);
PipelineInstanceModel pipeline = new PipelineInstanceModel(pipelineName, -2, "label", BuildCause.createManualForced(), new StageInstanceModels());
PipelineInstanceModels pims = PipelineInstanceModels.createPipelineInstanceModels(pipeline);
when(mockTemplate.queryForList("allActivePipelines")).thenReturn(pims);
when(mockTemplate.queryForObject(eq("getPipelineHistoryById"), any())).thenReturn(pipeline);
PipelineInstanceModels loaded;
loaded = pipelineDao.loadActivePipelines();
loaded = pipelineDao.loadActivePipelines();
assertNotSame(pipeline, loaded);
assertTrue(ToStringBuilder.reflectionToString(loaded) + " not equal to\n" + ToStringBuilder.reflectionToString(pipeline),
EqualsBuilder.reflectionEquals(loaded, pims));
verify(mockTemplate, times(1)).queryForList("allActivePipelines");
verify(mockTemplate,times(1)).queryForObject(eq("getPipelineHistoryById"), any());
verify(mockconfigFileDao,times(2)).load();
verify(mockCruiseConfig,times(2)).getAllPipelineNames();
}
private void changeStageStatus() {
changeStageStatus(99);
}
private void changeStageStatus(int pipelineId) {
Stage stage = new Stage();
stage.setName("stage");
stage.building();
changeStageStatus(stage, pipelineId);
}
private void changeStageStatus(Stage stage, int pipelineId) {
stage.setPipelineId((long) pipelineId);
stage.setIdentifier(new StageIdentifier("pipeline", 1, "blah-label", "stage", "1"));
pipelineDao.stageStatusChanged(stage);
}
@Test
public void shouldClearCachedPipelineHistoryWhenStageStatusChanges() {
PipelineInstanceModel pipeline = new PipelineInstanceModel("pipeline", -2, "label", BuildCause.createManualForced(), new StageInstanceModels());
when(mockTemplate.queryForObject(eq("getPipelineHistoryById"), any())).thenReturn(pipeline);
PipelineInstanceModel loaded;
loaded = pipelineDao.loadHistory(99);
changeStageStatus();
loaded = pipelineDao.loadHistory(99);
assertTrue(EqualsBuilder.reflectionEquals(loaded, pipeline));
verify(mockTemplate, times(2)).queryForObject(eq("getPipelineHistoryById"), any());
}
@Test
public void shouldIgnoreStageStatusChangeWhenActivePipelineCacheIsNotYetInitialized() {
changeStageStatus(1);
assertThat(goCache.get(pipelineDao.activePipelinesCacheKey()), is(nullValue()));
}
@Test
public void shouldCacheActivePipelineIdWhenStageStatusChanges() {
PipelineInstanceModel first = model(1, JobState.Building, JobResult.Unknown);
PipelineInstanceModel second = model(2, JobState.Building, JobResult.Unknown);
PipelineInstanceModels pims = PipelineInstanceModels.createPipelineInstanceModels(first);
when(mockTemplate.queryForList("allActivePipelines")).thenReturn(pims);
when(mockTemplate.queryForObject("getPipelineHistoryById", arguments("id", 1L).asMap())).thenReturn(first);
when(mockTemplate.queryForObject("getPipelineHistoryById", arguments("id", 2L).asMap())).thenReturn(second);
// ensure cache is initialized
pipelineDao.loadActivePipelines();
changeStageStatus(2);
pipelineDao.loadActivePipelines();
verify(mockTemplate, times(1)).queryForObject("getPipelineHistoryById", arguments("id", 1L).asMap());
verify(mockTemplate, times(1)).queryForObject("getPipelineHistoryById", arguments("id", 2L).asMap());
}
@Test
public void stageStatusChanged_shouldNotRaiseErrorWhenNoPipelinesAreActive() {
PipelineInstanceModel first = model(1, JobState.Building, JobResult.Unknown);
PipelineInstanceModels pims = PipelineInstanceModels.createPipelineInstanceModels();
when(mockTemplate.queryForList("allActivePipelines")).thenReturn(pims);
when(mockTemplate.queryForObject("getPipelineHistoryById", arguments("id", 1L).asMap())).thenReturn(first);
// ensure cache is initialized
pipelineDao.loadActivePipelines();
changeStageStatus(1);
pipelineDao.loadActivePipelines();
verify(mockTemplate, times(1)).queryForObject("getPipelineHistoryById", arguments("id", 1L).asMap());
}
@Test
public void shouldRemovePipelineIdFromCacheWhenStageFinishesForNonLatestPipeline() {
final String pipelineName = "pipeline";
CruiseConfig mockCruiseConfig=mock(BasicCruiseConfig.class);
GoConfigDao mockconfigFileDao = mock(GoConfigDao.class);
when(mockconfigFileDao.load()).thenReturn(mockCruiseConfig);
when(mockCruiseConfig.getAllPipelineNames()).thenReturn(Arrays.asList(new CaseInsensitiveString(pipelineName)));
//need to mock configfileDao for this test
pipelineDao = new PipelineSqlMapDao(null, repository, goCache, environmentVariableDao, transactionTemplate, null,
transactionSynchronizationManager, null, mockconfigFileDao, null, mock(SessionFactory.class));
pipelineDao.setSqlMapClientTemplate(mockTemplate);
PipelineInstanceModel first = model(1, JobState.Building, JobResult.Unknown);
PipelineInstanceModel second = model(2, JobState.Building, JobResult.Unknown);
PipelineInstanceModels pims = PipelineInstanceModels.createPipelineInstanceModels(first, second);
when(mockTemplate.queryForList("allActivePipelines")).thenReturn(pims);
when(mockTemplate.queryForObject("getPipelineHistoryById", arguments("id", 1L).asMap())).thenReturn(first);
when(mockTemplate.queryForObject("getPipelineHistoryById", arguments("id", 2L).asMap())).thenReturn(second);
// ensure cache is initialized
pipelineDao.loadActivePipelines();
Stage stage = new Stage("first", new JobInstances(JobInstanceMother.assigned("job")), "me", "whatever", new TimeProvider());
stage.fail();
stage.calculateResult();
changeStageStatus(stage, 1);
//notifying latest id should not remove it from the cache
stage.setPipelineId(2l);
pipelineDao.stageStatusChanged(stage);
PipelineInstanceModels models = pipelineDao.loadActivePipelines();
assertThat(models.size(), is(1));
assertThat(models.get(0).getId(), is(2L));
}
@Test
public void shouldRemovePipelineIdFromCacheWhenPipelineCeasesToBeTheLatestAndIsNotActive() {
final String pipelineName = "pipeline";
CruiseConfig mockCruiseConfig=mock(BasicCruiseConfig.class);
GoConfigDao mockconfigFileDao = mock(GoConfigDao.class);
when(mockconfigFileDao.load()).thenReturn(mockCruiseConfig);
when(mockCruiseConfig.getAllPipelineNames()).thenReturn(Arrays.asList(new CaseInsensitiveString(pipelineName)));
//need to mock configfileDao for this test
pipelineDao = new PipelineSqlMapDao(null, repository, goCache, environmentVariableDao, transactionTemplate, null,
transactionSynchronizationManager, null, mockconfigFileDao, null, mock(SessionFactory.class));
pipelineDao.setSqlMapClientTemplate(mockTemplate);
PipelineInstanceModel first = model(1, JobState.Completed, JobResult.Passed);
PipelineInstanceModel second = model(2, JobState.Building, JobResult.Unknown);
PipelineInstanceModels pims = PipelineInstanceModels.createPipelineInstanceModels(first);
when(mockTemplate.queryForList("allActivePipelines")).thenReturn(pims);
when(mockTemplate.queryForObject("getPipelineHistoryById", arguments("id", 1L).asMap())).thenReturn(first);
when(mockTemplate.queryForObject("getPipelineHistoryById", arguments("id", 2L).asMap())).thenReturn(second);
// ensure cache is initialized
pipelineDao.loadActivePipelines();
changeStageStatus(2);
PipelineInstanceModels models = pipelineDao.loadActivePipelines();
assertThat(models.size(), is(1));
assertThat(models.get(0).getId(), is(2L));
}
@Test
public void shouldCachePipelinePauseState() {
String pipelineName = "pipelineName";
PipelinePauseInfo pauseInfo = new PipelinePauseInfo(true, "pause cause", "admin");
when(mockTemplate.queryForObject("getPipelinePauseState", pipelineName)).thenReturn(pauseInfo);
// ensure cache is initialized
pipelineDao.pauseState(pipelineName);
PipelinePauseInfo actualPauseInfo = pipelineDao.pauseState(pipelineName);
assertThat(actualPauseInfo, is(pauseInfo));
verify(mockTemplate, times(1)).queryForObject("getPipelinePauseState", pipelineName);
}
@Test
public void shouldCacheLatestPassedStageForPipeline() {
StageIdentifier identifier = new StageIdentifier();
long pipelineId = 10;
String stage = "stage";
Map<String, Object> args = arguments("id", pipelineId).and("stage", stage).asMap();
when(mockTemplate.queryForObject("latestPassedStageForPipelineId", args)).thenReturn(identifier);
pipelineDao.latestPassedStageIdentifier(pipelineId, stage);
StageIdentifier actual = pipelineDao.latestPassedStageIdentifier(pipelineId, stage);
assertThat(actual, is(identifier));
verify(mockTemplate, times(1)).queryForObject("latestPassedStageForPipelineId", args);
}
@Test
public void shouldCacheNullStageIdentifierIfNoneOfTheRunsForStageHasEverPassed() {
StageIdentifier identifier = new StageIdentifier();
long pipelineId = 10;
String stage = "stage";
Map<String, Object> args = arguments("id", pipelineId).and("stage", stage).asMap();
when(mockTemplate.queryForObject("latestPassedStageForPipelineId", args)).thenReturn(null);
StageIdentifier actual = pipelineDao.latestPassedStageIdentifier(pipelineId, stage);
assertThat(actual, is(StageIdentifier.NULL));
assertThat(goCache.get(pipelineDao.cacheKeyForlatestPassedStage(pipelineId, stage)), is(StageIdentifier.NULL));
actual = pipelineDao.latestPassedStageIdentifier(pipelineId, stage);
assertThat(actual, is(StageIdentifier.NULL));
verify(mockTemplate, times(1)).queryForObject("latestPassedStageForPipelineId", args);
}
@Test
public void shouldRemoveLatestPassedStageForPipelineFromCacheUponStageStatusChangeCaseInsensitively() {
String stage = "stage";
Stage passedStage = StageMother.passedStageInstance(stage.toUpperCase(), "job", "pipeline-name");
passedStage.setPipelineId(10L);
goCache.put(pipelineDao.cacheKeyForlatestPassedStage(passedStage.getPipelineId(), stage), new StageIdentifier());
pipelineDao.stageStatusChanged(passedStage);
assertThat(goCache.get(pipelineDao.cacheKeyForlatestPassedStage(passedStage.getPipelineId(), stage)), is(nullValue()));
}
@Test
public void shouldInvalidateCacheWhenPipelineIsUnPaused() {
String pipelineName = "pipelineName";
Map<String, Object> args = arguments("pipelineName", pipelineName).and("pauseCause", null).and("pauseBy", null).and("paused", false).asMap();
when(mockTemplate.update("updatePipelinePauseState", args)).thenReturn(0);
when(mockTemplate.queryForObject("getPipelinePauseState", pipelineName)).thenReturn(PipelinePauseInfo.notPaused());
// ensure cache is initialized
pipelineDao.pauseState(pipelineName);
pipelineDao.unpause(pipelineName);
PipelinePauseInfo actualPauseInfo = pipelineDao.pauseState(pipelineName);
assertThat(actualPauseInfo, is(PipelinePauseInfo.notPaused()));
verify(mockTemplate, times(2)).queryForObject("getPipelinePauseState", pipelineName);
}
@Test
public void shouldInvalidateCacheWhenPipelineIsPausedCaseInsensitively() {
String pipelineName = "pipelineName";
String pauseBy = "foo";
String pauseCause = "waiting";
Map<String, Object> args = arguments("pipelineName", pipelineName).and("pauseCause", pauseCause).and("pauseBy", pauseBy).and("paused", true).asMap();
when(mockTemplate.update("updatePipelinePauseState", args)).thenReturn(0);
PipelinePauseInfo notPausedPipelineInfo = new PipelinePauseInfo(true, pauseCause, pauseBy);
when(mockTemplate.queryForObject("getPipelinePauseState", pipelineName)).thenReturn(notPausedPipelineInfo);
PipelinePauseInfo pausedPipelineInfo = new PipelinePauseInfo(true, pauseCause, pauseBy);
when(mockTemplate.queryForObject("getPipelinePauseState", pipelineName.toUpperCase())).thenReturn(pausedPipelineInfo);
assertThat(pipelineDao.pauseState(pipelineName), is(pausedPipelineInfo));
pipelineDao.pause(pipelineName.toUpperCase(), pauseCause, pauseBy);
assertThat(pipelineDao.pauseState(pipelineName), is(pausedPipelineInfo));
verify(mockTemplate, times(2)).queryForObject("getPipelinePauseState", pipelineName);
verify(mockTemplate).queryForObject("getPipelinePauseState", pipelineName.toUpperCase());
verify(mockTemplate).update(eq("updatePipelinePauseState"), anyObject());
}
@Test
public void shouldCachePipelineInstancesTriggeredOutOfDependencyMaterialCaseInsensitively() throws Exception {
List<PipelineIdentifier> results = Arrays.asList(new PipelineIdentifier("p1", 1));
when(mockTemplate.queryForList(eq("pipelineInstancesTriggeredOutOfDependencyMaterial"), anyString())).thenReturn(results);
pipelineDao.getPipelineInstancesTriggeredWithDependencyMaterial("p1", new PipelineIdentifier("p", 1));
//Query second time should return from cache
pipelineDao.getPipelineInstancesTriggeredWithDependencyMaterial("p1".toUpperCase(), new PipelineIdentifier("P", 1));
verify(mockTemplate, times(1)).queryForList(eq("pipelineInstancesTriggeredOutOfDependencyMaterial"), anyString());
}
@Test
public void shouldCacheEmptyPipelineInstancesTriggeredOutOfDependencyMaterial() throws Exception {
String cacheKey = (PipelineSqlMapDao.class + "_cacheKeyForPipelineInstancesWithDependencyMaterial_" + "p1_p_1").intern();
when(mockTemplate.queryForList(eq("pipelineInstancesTriggeredOutOfDependencyMaterial"), anyString())).thenReturn(new ArrayList());
List<PipelineIdentifier> actual = pipelineDao.getPipelineInstancesTriggeredWithDependencyMaterial("p1", new PipelineIdentifier("p", 1));
assertThat(actual, hasSize(0));
assertThat(goCache.get(cacheKey), is(actual));
}
@Test
public void shouldInvalidateCacheOfPipelineInstancesTriggeredWithDependencyMaterial() throws Exception {
String cacheKey = (PipelineSqlMapDao.class + "_cacheKeyForPipelineInstancesWithDependencyMaterial_" + "p1_p_1").intern();
List<PipelineIdentifier> result = Arrays.asList(new PipelineIdentifier("p1", 1, "1"));
when(mockTemplate.queryForList(eq("pipelineInstancesTriggeredOutOfDependencyMaterial"), anyString())).thenReturn(new ArrayList());
List<PipelineIdentifier> actual = pipelineDao.getPipelineInstancesTriggeredWithDependencyMaterial("p1", new PipelineIdentifier("p", 1));
assertThat(actual, hasSize(0));
assertThat((List<PipelineIdentifier>) goCache.get(cacheKey), hasSize(0));
MaterialRevisions materialRevisions = new MaterialRevisions(
new MaterialRevision(new DependencyMaterial(new CaseInsensitiveString("p"), new CaseInsensitiveString("s")), new Modification("u", "comment", "email", new Date(), "p/1/s/1")));
Pipeline pipeline = new Pipeline("p1", BuildCause.createWithModifications(materialRevisions, ""));
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
((TransactionSynchronizationAdapter) invocation.getArguments()[0]).afterCommit();
return null;
}
}).when(transactionSynchronizationManager).registerSynchronization(any(TransactionSynchronization.class));
when(transactionTemplate.execute(any(TransactionCallback.class))).then(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
((TransactionCallback) invocation.getArguments()[0]).doInTransaction(new SimpleTransactionStatus());
return null;
}
});
pipelineDao.save(pipeline);
assertThat(goCache.get(cacheKey), is(Matchers.nullValue()));
}
@Test
public void shouldNotInvalidateCacheOfPipelineInstancesTriggeredWithDependencyMaterial_WhenADifferentPipelineInstanceIsCreatedWithDifferentRevision() throws Exception {
String cacheKey = (PipelineSqlMapDao.class + "_cacheKeyForPipelineInstancesWithDependencyMaterial_" + "p1_p_1").intern();
List<PipelineIdentifier> result = Arrays.asList(new PipelineIdentifier("p1", 1, "1"));
when(mockTemplate.queryForList(eq("pipelineInstancesTriggeredOutOfDependencyMaterial"), anyString())).thenReturn(result);
List<PipelineIdentifier> actual = pipelineDao.getPipelineInstancesTriggeredWithDependencyMaterial("p1", new PipelineIdentifier("p", 1));
assertThat(actual, Matchers.is(result));
assertThat(goCache.get(cacheKey), is(result));
MaterialRevisions materialRevisions = new MaterialRevisions(
new MaterialRevision(new DependencyMaterial(new CaseInsensitiveString("p"), new CaseInsensitiveString("s")), new Modification("u", "comment", "email", new Date(), "p/2/s/1")));
Pipeline pipeline = new Pipeline("p1", BuildCause.createWithModifications(materialRevisions, ""));
pipelineDao.save(pipeline);
assertThat(goCache.get(cacheKey), is(result));
}
@Test
public void shouldCachePipelineInstancesTriggeredOutOfMaterialRevision() throws Exception {
GitMaterialInstance materialInstance = new GitMaterialInstance("url", "branch", "submodule", "flyweight");
List<PipelineIdentifier> results = Arrays.asList(new PipelineIdentifier("p1", 1));
String cacheKey = pipelineDao.cacheKeyForPipelineInstancesTriggeredWithDependencyMaterial("p1", materialInstance.getFingerprint(), "r1");
when(mockTemplate.queryForList(eq("pipelineInstancesTriggeredOffOfMaterialRevision"), anyString())).thenReturn(results);
assertThat(goCache.get(cacheKey), is(Matchers.nullValue()));
List<PipelineIdentifier> actual = pipelineDao.getPipelineInstancesTriggeredWithDependencyMaterial("p1", materialInstance, "r1");
//Query second time should return from cache
pipelineDao.getPipelineInstancesTriggeredWithDependencyMaterial("p1".toUpperCase(), materialInstance, "r1");
assertThat(goCache.get(cacheKey), is(actual));
verify(mockTemplate, times(1)).queryForList(eq("pipelineInstancesTriggeredOffOfMaterialRevision"), anyString());
}
@Test
public void shouldCacheEmptyPipelineInstancesTriggeredOutOfMaterialRevision() throws Exception {
GitMaterialInstance materialInstance = new GitMaterialInstance("url", "branch", "submodule", "flyweight");
String cacheKey = pipelineDao.cacheKeyForPipelineInstancesTriggeredWithDependencyMaterial("p1", materialInstance.getFingerprint(), "r1");
when(mockTemplate.queryForList(eq("pipelineInstancesTriggeredOffOfMaterialRevision"), anyString())).thenReturn(new ArrayList());
List<PipelineIdentifier> actual = pipelineDao.getPipelineInstancesTriggeredWithDependencyMaterial("p1", materialInstance, "r1");
assertThat(actual, hasSize(0));
assertThat(goCache.get(cacheKey), is(actual));
}
@Test
public void shouldInvalidateCacheOfPipelineInstancesTriggeredWithMaterialRevision() throws Exception {
GitMaterialInstance materialInstance = new GitMaterialInstance("url", "branch", "submodule", "flyweight");
String cacheKey = pipelineDao.cacheKeyForPipelineInstancesTriggeredWithDependencyMaterial("p1", materialInstance.getFingerprint(), "r1");
List<PipelineIdentifier> result = Arrays.asList(new PipelineIdentifier("p1", 1, "1"));
when(mockTemplate.queryForList(eq("pipelineInstancesTriggeredOffOfMaterialRevision"), anyString())).thenReturn(result);
List<PipelineIdentifier> actual = pipelineDao.getPipelineInstancesTriggeredWithDependencyMaterial("p1", materialInstance, "r1");
assertThat(actual, hasSize(1));
assertThat((List<PipelineIdentifier>) goCache.get(cacheKey), hasSize(1));
MaterialRevisions materialRevisions = new MaterialRevisions(new MaterialRevision(new GitMaterial("url", "branch"), new Modification("user", "comment", "email", new Date(), "r1")));
Pipeline pipeline = new Pipeline("p1", BuildCause.createWithModifications(materialRevisions, ""));
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
((TransactionSynchronizationAdapter) invocation.getArguments()[0]).afterCommit();
return null;
}
}).when(transactionSynchronizationManager).registerSynchronization(any(TransactionSynchronization.class));
when(transactionTemplate.execute(any(TransactionCallback.class))).then(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
((TransactionCallback) invocation.getArguments()[0]).doInTransaction(new SimpleTransactionStatus());
return null;
}
});
pipelineDao.save(pipeline);
assertThat(goCache.get(cacheKey), is(Matchers.nullValue()));
}
@Test
public void shouldNotInvalidateCacheOfPipelineInstancesTriggeredWithMaterialRevision_WhenAPipelineInstanceIsCreatedWithDifferentMaterial() throws Exception {
GitMaterialInstance materialInstance = new GitMaterialInstance("url", "branch", "submodule", "flyweight");
String cacheKey = pipelineDao.cacheKeyForPipelineInstancesTriggeredWithDependencyMaterial("p1", materialInstance.getFingerprint(), "r1");
List<PipelineIdentifier> result = Arrays.asList(new PipelineIdentifier("p1", 1, "1"));
when(mockTemplate.queryForList(eq("pipelineInstancesTriggeredOffOfMaterialRevision"), anyString())).thenReturn(result);
List<PipelineIdentifier> actual = pipelineDao.getPipelineInstancesTriggeredWithDependencyMaterial("p1", materialInstance, "r1");
assertThat(actual, Matchers.is(result));
assertThat(goCache.get(cacheKey), is(result));
MaterialRevisions materialRevisions = new MaterialRevisions(new MaterialRevision(new GitMaterial("url", "branch"), new Modification("user", "comment", "email", new Date(), "r2")));
Pipeline pipeline = new Pipeline("p1", BuildCause.createWithModifications(materialRevisions, ""));
pipelineDao.save(pipeline);
assertThat(goCache.get(cacheKey), is(result));
}
private PipelineInstanceModel model(long id, JobState jobState, JobResult jobResult) {
StageInstanceModels models = new StageInstanceModels();
models.add(new StageInstanceModel("first", "1", JobHistory.withJob("job", jobState, jobResult, new Date())));
PipelineInstanceModel model = new PipelineInstanceModel("pipeline", -2, "label", BuildCause.createManualForced(), models);
model.setId(id);
return model;
}
}