/*
* 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.service;
import com.thoughtworks.go.config.GoConfigDao;
import com.thoughtworks.go.config.materials.git.GitMaterial;
import com.thoughtworks.go.domain.MaterialRevisions;
import com.thoughtworks.go.domain.buildcause.BuildCause;
import com.thoughtworks.go.domain.materials.ModifiedAction;
import com.thoughtworks.go.server.cache.GoCache;
import com.thoughtworks.go.server.dao.DatabaseAccessHelper;
import com.thoughtworks.go.server.domain.PipelineTimeline;
import com.thoughtworks.go.server.materials.DependencyMaterialUpdateNotifier;
import com.thoughtworks.go.server.persistence.MaterialRepository;
import com.thoughtworks.go.server.scheduling.BuildCauseProducerService;
import com.thoughtworks.go.server.service.result.ServerHealthStateOperationResult;
import com.thoughtworks.go.server.transaction.TransactionTemplate;
import com.thoughtworks.go.util.GoConfigFileHelper;
import com.thoughtworks.go.util.LogFixture;
import com.thoughtworks.go.util.SystemEnvironment;
import com.thoughtworks.go.util.TestFileUtil;
import org.apache.log4j.Level;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
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.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Date;
import static com.thoughtworks.go.util.LogFixture.logFixtureFor;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
@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 BuildCauseProducerServiceIntegrationTimerTest {
public static final String STAGE_NAME = "s";
@Autowired
private DatabaseAccessHelper dbHelper;
@Autowired
private GoCache goCache;
@Autowired
private GoConfigDao goConfigDao;
@Autowired
private BuildCauseProducerService buildCauseProducerService;
@Autowired
private MaterialRepository materialRepository;
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private SystemEnvironment systemEnvironment;
@Autowired
private PipelineTimeline pipelineTimeline;
@Autowired
private PipelineScheduleQueue piplineScheduleQueue;
@Autowired
private DependencyMaterialUpdateNotifier notifier;
private GoConfigFileHelper configHelper = new GoConfigFileHelper();
private ScheduleTestUtil u;
@Before
public void setUp() throws Exception {
goCache.clear();
configHelper.usingCruiseConfigDao(goConfigDao);
configHelper.onSetUp();
dbHelper.onSetUp();
u = new ScheduleTestUtil(transactionTemplate, materialRepository, dbHelper, configHelper);
notifier.disableUpdates();
}
@After
public void teardown() throws Exception {
notifier.enableUpdates();
systemEnvironment.reset(SystemEnvironment.RESOLVE_FANIN_MAX_BACK_TRACK_LIMIT);
dbHelper.onTearDown();
configHelper.onTearDown();
piplineScheduleQueue.clear();
}
@Test
public void pipelineWithTimerShouldRunWithLatestMaterialsWhenItHasBeenForceRunWithOlderMaterials_GivenTimerIsSetToTriggerOnlyForNewMaterials() throws Exception {
int i = 1;
String pipelineName = "p1";
GitMaterial git1 = u.wf(new GitMaterial("git1"), "folder");
ScheduleTestUtil.AddedPipeline p1 = u.saveConfigWithTimer(pipelineName, u.timer("* * * * * ? 2000", true), u.m(git1));
u.checkinFile(git1, "g11", TestFileUtil.createTempFile("blah_g11"), ModifiedAction.added);
u.checkinFile(git1, "g12", TestFileUtil.createTempFile("blah_g12"), ModifiedAction.added);
Date mduTimeForG11 = u.d(i++);
u.runAndPassWithGivenMDUTimestampAndRevisionStrings(p1, mduTimeForG11, "g11");
u.runAndPassWithGivenMDUTimestampAndRevisionStrings(p1, u.d(i++), "g12");
u.runAndPassWithGivenMDUTimestampAndRevisionStrings(p1, mduTimeForG11, "g11");
pipelineTimeline.update();
buildCauseProducerService.timerSchedulePipeline(p1.config, new ServerHealthStateOperationResult());
assertThat(piplineScheduleQueue.toBeScheduled().size(), is(1));
assertThat(piplineScheduleQueue.toBeScheduled().get(pipelineName).getMaterialRevisions(), isSameMaterialRevisionsAs(u.mrs(u.mr(git1, false, "g12"))));
}
@Test
public void pipelineWithTimerShouldRerunWhenItHasAlreadyRunWithLatestMaterials_GivenTimerIsNOTSetToTriggerOnlyForNewMaterials() throws Exception {
int i = 1;
String pipelineName = "p1";
GitMaterial git1 = u.wf(new GitMaterial("git1"), "folder");
ScheduleTestUtil.AddedPipeline up1 = u.saveConfigWith("up1", u.m(git1));
ScheduleTestUtil.AddedPipeline p1 = u.saveConfigWithTimer(pipelineName, u.timer("* * * * * ? 2000", false), u.m(git1), u.m(up1));
u.checkinFile(git1, "g11", TestFileUtil.createTempFile("blah_g11"), ModifiedAction.added);
String up1_1 = u.runAndPassWithGivenMDUTimestampAndRevisionStrings(up1, u.d(i++), "g11");
pipelineTimeline.update();
// Run once with latest, when pipeline schedules due to timer.
buildCauseProducerService.timerSchedulePipeline(p1.config, new ServerHealthStateOperationResult());
assertThat(piplineScheduleQueue.toBeScheduled().size(), is(1));
assertThat(piplineScheduleQueue.toBeScheduled().get(pipelineName).getMaterialRevisions(), isSameMaterialRevisionsAs(u.mrs(u.mr(git1, true, "g11"), u.mr(up1, true, up1_1))));
BuildCause buildCause = piplineScheduleQueue.toBeScheduled().get(pipelineName);
String up1_2 = u.runAndPassWithGivenMDUTimestampAndRevisionStrings(up1, u.d(i++), "g11");
piplineScheduleQueue.finishSchedule(pipelineName, buildCause, buildCause);
try (LogFixture logFixture = logFixtureFor(TimedBuild.class, Level.INFO)) {
// Timer time comes around again. Will rerun since the new flag (runOnlyOnNewMaterials) is not ON.
buildCauseProducerService.timerSchedulePipeline(p1.config, new ServerHealthStateOperationResult());
assertThat(piplineScheduleQueue.toBeScheduled().size(), is(1));
assertThat(piplineScheduleQueue.toBeScheduled().get(pipelineName).getMaterialRevisions(), is(u.mrs(u.mr(git1, false, "g11"), u.mr(up1, false, up1_2))));
assertThat(logFixture.contains(Level.INFO, "Skipping scheduling of timer-triggered pipeline 'p1' as it has previously run with the latest material(s)."), is(false));
}
}
@Test
public void pipelineWithTimerShouldNotRerunWhenItHasAlreadyRunWithLatestMaterials_GivenTimerIsSetToTriggerOnlyForNewMaterials() throws Exception {
String pipelineName = "p1";
GitMaterial git1 = u.wf(new GitMaterial("git1"), "folder1");
GitMaterial git2 = u.wf(new GitMaterial("git2"), "folder2");
ScheduleTestUtil.AddedPipeline p1 = u.saveConfigWithTimer(pipelineName, u.timer("* * * * * ? 2000", true), u.m(git1), u.m(git2));
u.checkinFile(git1, "g11", TestFileUtil.createTempFile("blah_g11"), ModifiedAction.added);
u.checkinFile(git2, "g21", TestFileUtil.createTempFile("blah_g21"), ModifiedAction.added);
// Run once with latest, when pipeline schedules due to timer.
buildCauseProducerService.timerSchedulePipeline(p1.config, new ServerHealthStateOperationResult());
assertThat(piplineScheduleQueue.toBeScheduled().size(), is(1));
assertThat(piplineScheduleQueue.toBeScheduled().get(pipelineName).getMaterialRevisions(), isSameMaterialRevisionsAs(u.mrs(u.mr(git1, true, "g11"), u.mr(git2, true, "g21"))));
BuildCause buildCause = piplineScheduleQueue.toBeScheduled().get(pipelineName);
piplineScheduleQueue.finishSchedule(pipelineName, buildCause, buildCause);
try (LogFixture logFixture = logFixtureFor(TimedBuild.class, Level.INFO)) {
// Timer time comes around again. Will NOT rerun since the new flag (runOnlyOnNewMaterials) is ON.
buildCauseProducerService.timerSchedulePipeline(p1.config, new ServerHealthStateOperationResult());
assertThat(piplineScheduleQueue.toBeScheduled().size(), is(0));
assertThat(logFixture.contains(Level.INFO, "Skipping scheduling of timer-triggered pipeline 'p1' as it has previously run with the latest material(s)."), is(true));
}
}
@Test
public void pipelineWithTimerShouldRunOnlyWithMaterialsWhichChanged_GivenTimerIsSetToTriggerOnlyForNewMaterials() throws Exception {
int i = 1;
String pipelineName = "p1";
GitMaterial git1 = u.wf(new GitMaterial("git1"), "folder1");
GitMaterial git2 = u.wf(new GitMaterial("git2"), "folder2");
ScheduleTestUtil.AddedPipeline p1 = u.saveConfigWith("up1", u.m(git1));
ScheduleTestUtil.AddedPipeline p2 = u.saveConfigWithTimer(pipelineName, u.timer("* * * * * ? 2000", true), u.m(git1), u.m(git2), u.m(p1));
u.checkinFile(git1, "g11", TestFileUtil.createTempFile("blah_g11"), ModifiedAction.added);
u.checkinFile(git2, "g21", TestFileUtil.createTempFile("blah_g21"), ModifiedAction.added);
Date mduTimeOfG11 = u.d(i++);
String p1_1 = u.runAndPassWithGivenMDUTimestampAndRevisionStrings(p1, mduTimeOfG11, "g11");
pipelineTimeline.update();
// Run once with latest, when pipeline schedules due to timer.
buildCauseProducerService.timerSchedulePipeline(p2.config, new ServerHealthStateOperationResult());
assertThat(piplineScheduleQueue.toBeScheduled().size(), is(1));
assertThat(piplineScheduleQueue.toBeScheduled().get(pipelineName).getMaterialRevisions(), isSameMaterialRevisionsAs(u.mrs(u.mr(git1, true, "g11"), u.mr(git2, true, "g21"), u.mr(p1, true, p1_1))));
BuildCause buildCause = piplineScheduleQueue.toBeScheduled().get(pipelineName);
u.runAndPassWithGivenMDUTimestampAndRevisionStrings(p2, u.d(i++), "g11", "g21", p1_1);
piplineScheduleQueue.finishSchedule(pipelineName, buildCause, buildCause);
// Check in to git2 and run pipeline P1 once before the timer time. Then, timer happens. Shows those two materials in "yellow" (changed), on the UI.
u.checkinFile(git2, "g22", TestFileUtil.createTempFile("blah_g22"), ModifiedAction.added);
String p1_2 = u.runAndPassWithGivenMDUTimestampAndRevisionStrings(p1, mduTimeOfG11, "g11");
pipelineTimeline.update();
try (LogFixture logFixture = logFixtureFor(TimedBuild.class, Level.INFO)) {
buildCauseProducerService.timerSchedulePipeline(p2.config, new ServerHealthStateOperationResult());
assertThat(piplineScheduleQueue.toBeScheduled().size(), is(1));
assertThat(piplineScheduleQueue.toBeScheduled().get(pipelineName).getMaterialRevisions(), isSameMaterialRevisionsAs(u.mrs(u.mr(git1, false, "g11"), u.mr(git2, true, "g22"), u.mr(p1, true, p1_2))));
assertThat(logFixture.contains(Level.INFO, "Skipping scheduling of timer-triggered pipeline 'p1' as it has previously run with the latest material(s)."), is(false));
}
}
private Matcher<? super MaterialRevisions> isSameMaterialRevisionsAs(final MaterialRevisions expectedRevisions) {
return new BaseMatcher<Object>() {
@Override
public boolean matches(Object o) {
MaterialRevisions actualRevisions = (MaterialRevisions) o;
return actualRevisions.equals(expectedRevisions) && allMaterialRevisionChangedFlagsMatch(expectedRevisions, actualRevisions);
}
private boolean allMaterialRevisionChangedFlagsMatch(MaterialRevisions expectedRevisions, MaterialRevisions actualRevisions) {
for (int i = 0; i < expectedRevisions.numberOfRevisions(); i++) {
if (expectedRevisions.getMaterialRevision(i).isChanged() != actualRevisions.getMaterialRevision(i).isChanged()) {
return false;
}
}
return true;
}
@Override
public void describeTo(Description description) {
description.appendValue(expectedRevisions);
}
};
}
}