/* * 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.GoConfigDao; import com.thoughtworks.go.database.Database; import com.thoughtworks.go.domain.Pipeline; import com.thoughtworks.go.domain.PipelineState; import com.thoughtworks.go.domain.StageIdentifier; import com.thoughtworks.go.helper.GoConfigMother; import com.thoughtworks.go.helper.PipelineMother; 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 org.hibernate.SessionFactory; import org.hibernate.classic.Session; 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.UUID; import static com.thoughtworks.go.domain.PipelineState.NOT_LOCKED; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.nullValue; import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; public class PipelineStateDaoCachingTest { private GoCache goCache; private PipelineStateDao pipelineStateDao; 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); pipelineStateDao = new PipelineStateDao(null, repository, goCache, environmentVariableDao, transactionTemplate, null, transactionSynchronizationManager, null, configFileDao, mock(Database.class), mockSessionFactory); pipelineStateDao.setSqlMapClientTemplate(mockTemplate); session = mock(Session.class); when(mockSessionFactory.getCurrentSession()).thenReturn(session); when(configFileDao.load()).thenReturn(GoConfigMother.defaultCruiseConfig()); } @Test public void lockedPipelineCacheKey_shouldReturnTheSameInstanceForAGivenPipeline() { assertSame( pipelineStateDao.pipelineLockStateCacheKey("dev"), pipelineStateDao.pipelineLockStateCacheKey("dev")); } @Test public void lockedPipeline_shouldCacheLockedPipelineStatus() throws Exception { String pipelineName = "mingle"; when(transactionTemplate.execute(any(org.springframework.transaction.support.TransactionCallback.class))).thenReturn(new PipelineState(pipelineName, new StageIdentifier(pipelineName, 1, "1", 1L, "s1", "1"))); PipelineState pipelineState = pipelineStateDao.pipelineStateFor(pipelineName); assertThat(goCache.get(pipelineStateDao.pipelineLockStateCacheKey(pipelineName)), is(pipelineState)); PipelineState secondAttempt = pipelineStateDao.pipelineStateFor(pipelineName); assertSame(pipelineState, secondAttempt); verify(transactionTemplate, times(1)).execute(any(TransactionCallback.class)); } @Test public void lockedPipeline_shouldReturnNullIfPipelineIsNotLocked() throws Exception { String pipelineName = UUID.randomUUID().toString(); pipelineStateDao.pipelineStateFor(pipelineName); PipelineState actual = pipelineStateDao.pipelineStateFor(pipelineName); assertNull("got " + actual, actual); assertThat(goCache.get(pipelineStateDao.pipelineLockStateCacheKey(pipelineName)), is(NOT_LOCKED)); verify(transactionTemplate, times(1)).execute(any(org.springframework.transaction.support.TransactionCallback.class)); } @Test public void lockPipeline_ShouldSavePipelineStateAndInvalidateCache() throws Exception { final TransactionSynchronizationAdapter[] transactionSynchronizationAdapter = {null}; when(transactionTemplate.execute(any(org.springframework.transaction.support.TransactionCallbackWithoutResult.class))).thenAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { org.springframework.transaction.support.TransactionCallbackWithoutResult callback = (org.springframework.transaction.support.TransactionCallbackWithoutResult) invocation.getArguments()[0]; callback.doInTransaction(new SimpleTransactionStatus()); transactionSynchronizationAdapter[0].afterCommit(); return null; } }); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { TransactionSynchronizationAdapter adapter= (TransactionSynchronizationAdapter) invocation.getArguments()[0]; transactionSynchronizationAdapter[0] = adapter; return null; } }).when(transactionSynchronizationManager).registerSynchronization(any(TransactionSynchronization.class)); final Pipeline pipeline = PipelineMother.pipeline("mingle"); PipelineState pipelineState = new PipelineState(pipeline.getName(), pipeline.getFirstStage().getIdentifier()); when(session.load(PipelineState.class, pipeline.getId())).thenReturn(pipelineState); goCache.put(pipelineStateDao.pipelineLockStateCacheKey(pipeline.getName()), pipelineState); pipelineStateDao.lockPipeline(pipeline); assertThat(goCache.get(pipelineStateDao.pipelineLockStateCacheKey(pipeline.getName())), is(nullValue())); ArgumentCaptor<PipelineState> pipelineStateArgumentCaptor = ArgumentCaptor.forClass(PipelineState.class); verify(session).saveOrUpdate(pipelineStateArgumentCaptor.capture()); PipelineState savedPipelineState = pipelineStateArgumentCaptor.getValue(); assertThat(savedPipelineState.isLocked(), is(true)); assertThat(savedPipelineState.getLockedByPipelineId(), is(pipeline.getId())); } @Test public void lockPipeline_shouldHandleSerializationProperly() throws Exception { when(mockTemplate.queryForObject(eq("lockedPipeline"), any())).thenReturn(null); assertNull(pipelineStateDao.pipelineStateFor("mingle")); goCache.put(pipelineStateDao.pipelineLockStateCacheKey("mingle"), NOT_LOCKED); assertNull(pipelineStateDao.pipelineStateFor("mingle")); } @Test public void unlockPipeline_shouldSavePipelineStateAndInvalidateCache() throws Exception { final TransactionSynchronizationAdapter[] transactionSynchronizationAdapter = {null}; when(transactionTemplate.execute(any(org.springframework.transaction.support.TransactionCallbackWithoutResult.class))).thenAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { org.springframework.transaction.support.TransactionCallbackWithoutResult callback = (org.springframework.transaction.support.TransactionCallbackWithoutResult) invocation.getArguments()[0]; callback.doInTransaction(new SimpleTransactionStatus()); transactionSynchronizationAdapter[0].afterCommit(); return null; } }); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { TransactionSynchronizationAdapter adapter= (TransactionSynchronizationAdapter) invocation.getArguments()[0]; transactionSynchronizationAdapter[0] = adapter; return null; } }).when(transactionSynchronizationManager).registerSynchronization(any(TransactionSynchronization.class)); final Pipeline pipeline = PipelineMother.pipeline("mingle"); PipelineState pipelineState = new PipelineState(pipeline.getName(), pipeline.getFirstStage().getIdentifier()); goCache.put(pipelineStateDao.pipelineLockStateCacheKey(pipeline.getName()), pipelineState); when(session.load(PipelineState.class, pipeline.getId())).thenReturn(pipelineState); pipelineStateDao.unlockPipeline(pipeline.getName()); assertThat(goCache.get(pipelineStateDao.pipelineLockStateCacheKey(pipeline.getName())), is(nullValue())); ArgumentCaptor<PipelineState> pipelineStateArgumentCaptor = ArgumentCaptor.forClass(PipelineState.class); verify(session).saveOrUpdate(pipelineStateArgumentCaptor.capture()); PipelineState savedPipelineState = pipelineStateArgumentCaptor.getValue(); assertThat(savedPipelineState.isLocked(), is(false)); assertThat(savedPipelineState.getLockedBy(), is(nullValue())); } }