/*
* Copyright 2016 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.ArtifactPlans;
import com.thoughtworks.go.config.ArtifactPropertiesGenerators;
import com.thoughtworks.go.config.EnvironmentVariablesConfig;
import com.thoughtworks.go.config.Resources;
import com.thoughtworks.go.domain.*;
import com.thoughtworks.go.helper.JobInstanceMother;
import com.thoughtworks.go.server.cache.GoCache;
import com.thoughtworks.go.server.domain.JobStatusListener;
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.orm.ibatis.SqlMapClientTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.*;
import static com.thoughtworks.go.util.ArrayUtil.asList;
import static com.thoughtworks.go.util.IBatisUtil.arguments;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
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 JobInstanceSqlMapDaoCachingTest {
@Autowired private GoCache goCache;
@Autowired private JobInstanceSqlMapDao jobInstanceDao;
private SqlMapClientTemplate mockTemplate;
@Before
public void setup() {
mockTemplate = mock(SqlMapClientTemplate.class);
}
@After
public void tearDown() {
goCache.clear();
}
@Test
public void buildByIdWithTransitions_shouldCacheWhenQueriedFor() {
jobInstanceDao.setSqlMapClientTemplate(mockTemplate);
JobInstance job = JobInstanceMother.assigned("job");
job.setId(1L);
when(mockTemplate.queryForObject("buildByIdWithTransitions", 1L)).thenReturn(job);
JobInstance actual = jobInstanceDao.buildByIdWithTransitions(1L);
assertThat(actual, is(job));
assertThat(actual == job, is(false));
jobInstanceDao.buildByIdWithTransitions(1L);
verify(mockTemplate, times(1)).queryForObject("buildByIdWithTransitions", 1L);
}
@Test
public void buildByIdWithTransitions_shouldClearFromCacheOnUpdateStatusOfJob() {
jobInstanceDao.setSqlMapClientTemplate(mockTemplate);
JobInstance job = JobInstanceMother.assigned("job");
job.setId(1L);
when(mockTemplate.queryForObject("buildByIdWithTransitions", 1L)).thenReturn(job);
JobInstance actual = jobInstanceDao.buildByIdWithTransitions(1L);
assertThat(actual, is(job));
assertThat(actual == job, is(false));
jobInstanceDao.updateStateAndResult(job); //Must clear cahced job instance
jobInstanceDao.buildByIdWithTransitions(1L);
verify(mockTemplate, times(2)).queryForObject("buildByIdWithTransitions", 1L);
}
@Test
public void orderedScheduledBuilds_shouldNotCacheJobPlanWhichIsNoLongerScheduled() {
when(mockTemplate.queryForList(eq("scheduledPlanIds"))).thenReturn(Arrays.asList(1L, 2L));
final DefaultJobPlan firstJob = jobPlan(1);
List<JobPlan> expectedPlans = new ArrayList<JobPlan>() {{ add(firstJob); }};
when(mockTemplate.queryForObject("scheduledPlan", arguments("id", 1L).asMap())).thenReturn(firstJob);
when(mockTemplate.queryForObject("scheduledPlan", arguments("id", 2L).asMap())).thenReturn(null);
jobInstanceDao.setSqlMapClientTemplate(mockTemplate);
List<JobPlan> plans = jobInstanceDao.orderedScheduledBuilds();
assertThat(plans, is(expectedPlans));
verify(mockTemplate, times(2)).queryForObject(eq("scheduledPlan"), any());
verify(mockTemplate, times(1)).queryForList(eq("scheduledPlanIds"));
}
@Test
public void orderedScheduledBuilds_shouldCacheJobPlan() {
when(mockTemplate.queryForList(eq("scheduledPlanIds"))).thenReturn(Arrays.asList(1L, 2L));
final DefaultJobPlan firstJob = jobPlan(1);
final DefaultJobPlan secondJob = jobPlan(2);
List<JobPlan> expectedPlans = new ArrayList<JobPlan>() {{ add(firstJob); add(secondJob); }};
when(mockTemplate.queryForObject("scheduledPlan", arguments("id", 1L).asMap())).thenReturn(firstJob);
when(mockTemplate.queryForObject("scheduledPlan", arguments("id", 2L).asMap())).thenReturn(secondJob);
jobInstanceDao.setSqlMapClientTemplate(mockTemplate);
jobInstanceDao.orderedScheduledBuilds();
List<JobPlan> plans = jobInstanceDao.orderedScheduledBuilds();
assertThat(plans, is(expectedPlans));
verify(mockTemplate, times(2)).queryForObject(eq("scheduledPlan"), any());
verify(mockTemplate, times(2)).queryForList(eq("scheduledPlanIds"));
}
@Test
public void updateStatus_shouldRemoveCachedJobPlan() {
when(mockTemplate.queryForList(eq("scheduledPlanIds"))).thenReturn(Arrays.asList(1L));
final DefaultJobPlan firstJob = jobPlan(1);
List<JobPlan> expectedPlans = new ArrayList<JobPlan>() {{ add(firstJob);}};
when(mockTemplate.queryForObject("scheduledPlan", arguments("id", 1L).asMap())).thenReturn(firstJob);
jobInstanceDao.setSqlMapClientTemplate(mockTemplate);
jobInstanceDao.orderedScheduledBuilds();//populate the cache
JobInstance instance = instance(1);
jobInstanceDao.updateStateAndResult(instance);
List<JobPlan> plans = jobInstanceDao.orderedScheduledBuilds();
assertThat(plans, is(expectedPlans));
verify(mockTemplate, times(2)).queryForObject("scheduledPlan", arguments("id", 1L).asMap());//because the cache is cleared
verify(mockTemplate, times(2)).queryForList(eq("scheduledPlanIds"));
}
private JobInstance instance(long id) {
JobInstance instance = JobInstanceMother.jobInstance("first", "resource");
instance.setId(id);
return instance;
}
@Test
public void activeJobs_shouldCacheCurrentlyActiveJobIds() {
final ActiveJob first = new ActiveJob(1L, "pipeline", 1, "label", "stage", "job1");
final ActiveJob second = new ActiveJob(2L, "another", 2, "label", "stage", "job1");
List<ActiveJob> expectedJobs = Arrays.asList(first, second);
when(mockTemplate.queryForList("getActiveJobIds")).thenReturn(Arrays.asList(1L, 2L));
when(mockTemplate.queryForObject("getActiveJobById", arguments("id", 1L).asMap())).thenReturn(first);
when(mockTemplate.queryForObject("getActiveJobById", arguments("id", 2L).asMap())).thenReturn(second);
jobInstanceDao.setSqlMapClientTemplate(mockTemplate);
jobInstanceDao.activeJobs();//populate the cache
List<ActiveJob> activeJobs = jobInstanceDao.activeJobs();
assertThat(expectedJobs, is(activeJobs));
verify(mockTemplate, times(1)).queryForList("getActiveJobIds");
verify(mockTemplate, times(1)).queryForObject("getActiveJobById", arguments("id", 1L).asMap());
verify(mockTemplate, times(1)).queryForObject("getActiveJobById", arguments("id", 2L).asMap());
}
@Test
public void activeJobs_shouldRemoveCacheActiveJobOnUpdateJobStatus() {
final ActiveJob first = new ActiveJob(1L, "pipeline", 1, "label", "stage", "first");
final ActiveJob second = new ActiveJob(2L, "another", 2, "label", "stage", "job1");
List<ActiveJob> expectedJobs = Arrays.asList(first, second);
when(mockTemplate.queryForList("getActiveJobIds")).thenReturn(Arrays.asList(1L, 2L));
when(mockTemplate.queryForObject("getActiveJobById", arguments("id", 1L).asMap())).thenReturn(first);
when(mockTemplate.queryForObject("getActiveJobById", arguments("id", 2L).asMap())).thenReturn(second);
jobInstanceDao.setSqlMapClientTemplate(mockTemplate);
jobInstanceDao.activeJobs();//cache it first
jobInstanceDao.updateStateAndResult(instance(1L));//should remove from cache
List<ActiveJob> activeJobs = jobInstanceDao.activeJobs();
assertThat(expectedJobs, is(activeJobs));
verify(mockTemplate, times(2)).queryForList("getActiveJobIds");
verify(mockTemplate, times(2)).queryForObject("getActiveJobById", arguments("id", 1L).asMap());
verify(mockTemplate, times(1)).queryForObject("getActiveJobById", arguments("id", 2L).asMap());
}
@Test
public void activeJobs_shouldNotCacheAJobThatsNoLongerActive() {
final ActiveJob first = new ActiveJob(1L, "pipeline", 1, "label", "stage", "first");
List<ActiveJob> expectedJobs = Arrays.asList(first);
when(mockTemplate.queryForList("getActiveJobIds")).thenReturn(Arrays.asList(1L, 2L));
when(mockTemplate.queryForObject("getActiveJobById", arguments("id", 1L).asMap())).thenReturn(first);
when(mockTemplate.queryForObject("getActiveJobById", arguments("id", 2L).asMap())).thenReturn(null);
jobInstanceDao.setSqlMapClientTemplate(mockTemplate);
jobInstanceDao.activeJobs();//cache it first
jobInstanceDao.updateStateAndResult(instance(1L));//should remove from cache
List<ActiveJob> activeJobs = jobInstanceDao.activeJobs();
assertThat(expectedJobs, is(activeJobs));
verify(mockTemplate, times(2)).queryForList("getActiveJobIds");
verify(mockTemplate, times(2)).queryForObject("getActiveJobById", arguments("id", 1L).asMap());
}
@Test
public void shouldCacheJobIdentifier() throws Exception {
jobInstanceDao.setSqlMapClientTemplate(mockTemplate);
JobInstance job = JobInstanceMother.buildEndingWithState(JobState.Building, JobResult.Unknown, "config");
when(mockTemplate.queryForObject(eq("findJobId"), any(Map.class))).thenReturn(job.getIdentifier());
jobInstanceDao.findOriginalJobIdentifier(job.getIdentifier().getStageIdentifier(), job.getName());
jobInstanceDao.findOriginalJobIdentifier(job.getIdentifier().getStageIdentifier(), job.getName());
verify(mockTemplate, times(1)).queryForObject(eq("findJobId"), any(Map.class));
}
@Test
public void shouldClearJobIdentifierFromCacheWhenJobIsRescheduled() throws Exception {
jobInstanceDao.setSqlMapClientTemplate(mockTemplate);
JobInstance job = JobInstanceMother.buildEndingWithState(JobState.Building, JobResult.Unknown, "config");
when(mockTemplate.queryForObject(eq("findJobId"), any(Map.class))).thenReturn(job.getIdentifier());
jobInstanceDao.findOriginalJobIdentifier(job.getIdentifier().getStageIdentifier(), job.getName());
job.changeState(JobState.Rescheduled, new Date());
JobStatusListener listener = jobInstanceDao;
listener.jobStatusChanged(job);
jobInstanceDao.findOriginalJobIdentifier(job.getIdentifier().getStageIdentifier(), job.getName());
verify(mockTemplate, times(2)).queryForObject(eq("findJobId"), any(Map.class));
}
@Test
public void shouldnotClearJobIdentifierFromCacheForAnyOtherJobStateChangeOtherThanRescheduledAsTheBuildIdDoesNotChange() throws Exception {
jobInstanceDao.setSqlMapClientTemplate(mockTemplate);
JobInstance job = JobInstanceMother.buildEndingWithState(JobState.Building, JobResult.Unknown, "config");
when(mockTemplate.queryForObject(eq("findJobId"), any(Map.class))).thenReturn(job.getIdentifier());
jobInstanceDao.findOriginalJobIdentifier(job.getIdentifier().getStageIdentifier(), job.getName());
List<JobState> jobStatesForWhichCacheNeedsToBeMaintained = asList(JobState.Assigned, JobState.Building, JobState.Completed, JobState.Discontinued,
JobState.Paused, JobState.Scheduled, JobState.Preparing, JobState.Assigned.Unknown);
JobStatusListener listener = jobInstanceDao;
for (JobState jobState : jobStatesForWhichCacheNeedsToBeMaintained) {
job.changeState(jobState, new Date());
listener.jobStatusChanged(job);
}
jobInstanceDao.findOriginalJobIdentifier(job.getIdentifier().getStageIdentifier(), job.getName());
verify(mockTemplate, times(1)).queryForObject(eq("findJobId"), any(Map.class));
}
private DefaultJobPlan jobPlan(long id) {
return new DefaultJobPlan(new Resources(), new ArtifactPlans(), new ArtifactPropertiesGenerators(), id, null, null, new EnvironmentVariablesConfig(), new EnvironmentVariablesConfig(), null);
}
}