/*
* 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 com.thoughtworks.go.config.CaseInsensitiveString;
import com.thoughtworks.go.config.PipelineConfig;
import com.thoughtworks.go.config.StageConfig;
import com.thoughtworks.go.config.TimerConfig;
import com.thoughtworks.go.config.materials.MaterialConfigs;
import com.thoughtworks.go.config.materials.PackageMaterialConfig;
import com.thoughtworks.go.config.materials.ScmMaterial;
import com.thoughtworks.go.config.materials.dependency.DependencyMaterial;
import com.thoughtworks.go.domain.*;
import com.thoughtworks.go.domain.buildcause.BuildCause;
import com.thoughtworks.go.domain.materials.Material;
import com.thoughtworks.go.domain.materials.MaterialConfig;
import com.thoughtworks.go.domain.materials.Modification;
import com.thoughtworks.go.domain.materials.ModifiedAction;
import com.thoughtworks.go.domain.scm.SCM;
import com.thoughtworks.go.helper.PipelineConfigMother;
import com.thoughtworks.go.helper.StageConfigMother;
import com.thoughtworks.go.server.dao.DatabaseAccessHelper;
import com.thoughtworks.go.server.persistence.MaterialRepository;
import com.thoughtworks.go.server.transaction.TransactionTemplate;
import com.thoughtworks.go.util.GoConfigFileHelper;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.joda.time.DateTime;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import java.io.File;
import java.sql.SQLException;
import java.util.*;
import static com.thoughtworks.go.domain.config.CaseInsensitiveStringMother.str;
import static com.thoughtworks.go.util.DataStructureUtils.a;
public class ScheduleTestUtil {
public static final String DEFAULT_GROUP = "test-group";
private final TransactionTemplate transactionTemplate;
private final MaterialRepository materialRepository;
private final DatabaseAccessHelper dbHelper;
private final GoConfigFileHelper configHelper;
private Date date;
private Map<CaseInsensitiveString, Pipeline> triggeredPipelines;
public ScheduleTestUtil(TransactionTemplate transactionTemplate, MaterialRepository materialRepository, DatabaseAccessHelper dbHelper, GoConfigFileHelper configHelper) {
this.transactionTemplate = transactionTemplate;
this.materialRepository = materialRepository;
this.dbHelper = dbHelper;
this.configHelper = configHelper;
date = new Date();
triggeredPipelines = new HashMap<>();
}
public Date d(int extraHours) {
return new DateTime(date.getTime()).plusHours(extraHours).toDate();
}
private Material mw(Material material) {
material = AutoTriggerDependencyResolutionTest.CLONER.deepClone(material);
return material;
}
public Material mw(AddedPipeline pipeline) {
return mw(pipeline.material);
}
public MaterialRevision mr(Material material, final boolean changed, final String... revs) {
ArrayList<Modification> modifications = new ArrayList<>();
for (String rev : revs) {
Modification mod = modForRev(rev);
modifications.add(0, mod);
}
MaterialRevision materialRevision = new MaterialRevision(material, modifications);
if (changed) {
materialRevision.markAsChanged();
}
return materialRevision;
}
public MaterialRevision mr(AddedPipeline pipeline, final boolean changed, final String endRev) {
return mr(pipeline.material, changed, endRev);
}
public String runAndPassWithGivenMDUTimestampAndRevisionObjects(final AddedPipeline pipeline, final Date mduAt, final RevisionsForMaterial... revisions) {
final Pipeline instance = scheduleWith(pipeline, revisions);
return pass(pipeline, mduAt, instance);
}
public String runAndPass(final AddedPipeline pipeline, String... revisions) {
return runAndPassWithGivenMDUTimestampAndRevisionStrings(pipeline, date, revisions);
}
public String runAndPassWithGivenMDUTimestampAndRevisionStrings(final AddedPipeline pipeline, final Date mduAt, final String... revisions) {
final Pipeline instance = scheduleWith(pipeline, revisions);
return pass(pipeline, mduAt, instance);
}
public Pipeline runAndPassAndReturnPipelineInstance(final AddedPipeline pipeline, final Date mduAt, final String... revisions) {
final Pipeline instance = scheduleWith(pipeline, revisions);
pass(pipeline, mduAt, instance);
return instance;
}
public String runAndFail(final AddedPipeline pipeline, final Date mduAt, final String... revisions) {
final Pipeline instance = scheduleWith(pipeline, revisions);
return fail(pipeline, mduAt, instance);
}
public String rerunStageAndCancel(Pipeline pipeline, StageConfig stageConfig){
Stage stage = dbHelper.scheduleStage(pipeline, stageConfig);
dbHelper.cancelStage(stage);
return stage.getIdentifier().getStageLocator();
}
private String pass(final AddedPipeline pipeline, final Date mduAt, final Pipeline instance) {
dbHelper.pass(instance);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override protected void doInTransactionWithoutResult(TransactionStatus status) {
for (Stage stage : instance.getStages()) {
final String dmrRevStr = new StageIdentifier(new PipelineIdentifier(instance.getName(), instance.getCounter(), instance.getName() + instance.getCounter()), stage.getName(), "1").getStageLocator();
Modification modification = new Modification(mduAt, dmrRevStr, instance.getLabel(), instance.getId());
materialRepository.saveMaterialRevision(new MaterialRevision(pipeline.material, modification));
}
}
});
return new StageIdentifier(new PipelineIdentifier(instance.getName(), instance.getCounter(), instance.getName() + instance.getCounter()), pipeline.material.getStageName().toString(), "1").getStageLocator();
}
private String fail(final AddedPipeline pipeline, final Date mduAt, final Pipeline instance) {
dbHelper.failStage(instance.getFirstStage());
final String dmrRevStr = new StageIdentifier(new PipelineIdentifier(instance.getName(), instance.getCounter(), instance.getName() + instance.getCounter()), CaseInsensitiveString.str(
pipeline.material.getStageName()), "1").getStageLocator();
//should not add dmr to database because it failed.
return dmrRevStr;
}
public MaterialRevisions mrs(MaterialRevision... revs) {
MaterialRevisions revisions = new MaterialRevisions();
for (MaterialRevision rev : revs) {
revisions.addRevision(rev);
}
return revisions;
}
public <T extends ScmMaterial> T wf(T material, String folder) {
material.setFolder(folder);
return material;
}
public TimerConfig timer(String timerSpec, boolean shouldTriggerOnlyOnNewMaterials) {
return new TimerConfig(timerSpec, shouldTriggerOnlyOnNewMaterials);
}
public RevisionsForMaterial[] getRevisionsForMaterials(List<String>... materialRevisions) {
List<RevisionsForMaterial> revisionsForMaterials = new ArrayList<>();
for (List<String> materialRevision : materialRevisions) {
revisionsForMaterials.add(new RevisionsForMaterial(materialRevision));
}
return revisionsForMaterials.toArray(new RevisionsForMaterial[revisionsForMaterials.size()]);
}
public AddedPipeline renamePipelineAndFirstStage(AddedPipeline pipeline, String newPipelineName, String stageName) {
PipelineConfig oldConfig = pipeline.config;
configHelper.removePipeline(CaseInsensitiveString.str(oldConfig.name()));
PipelineConfig newPipeline = PipelineConfigMother.renamePipeline(oldConfig, newPipelineName);
StageConfigMother.renameStage(newPipeline.get(0), stageName);
configHelper.addPipeline(newPipeline);
return new AddedPipeline(newPipeline, pipeline.material);
}
protected static class RevisionsForMaterial {
List<String> revs;
private RevisionsForMaterial(List<String> revs) {
this.revs = revs;
}
@Override public String toString() {
return "RevisionsForMaterial{" +
"revs=" + revs +
'}';
}
}
public RevisionsForMaterial[] rs(String... revs) {
return new RevisionsForMaterial[]{new RevisionsForMaterial(Arrays.asList(revs))};
}
public List<String> revisions(String... revs) {
return Arrays.asList(revs);
}
private Pipeline scheduleWith(AddedPipeline pipeline, RevisionsForMaterial... revisions) {
Pipeline oldInstance = triggeredPipelines.get(pipeline.config.name());
Map<Material, List<Modification>> modMap = new HashMap<>();
Map<Modification, Modification> identityMap = new HashMap<>();
MaterialRevisions buildCause = new MaterialRevisions();
int i = 0;
for (Material material : new MaterialConfigConverter().toMaterials(pipeline.config.materialConfigs())) {
List<Modification> modifications = new ArrayList<>();
for (Modification modification : modForRev(revisions[i++])) {
if (!identityMap.containsKey(modification)) {
identityMap.put(modification, modification);
}
modifications.add(identityMap.get(modification));
}
MaterialInstance modificationsMaterialInstance = modifications.get(0).getMaterialInstance();
if (modMap.containsKey(material)) {
modifications = modMap.get(material);
} else {
modMap.put(material, modifications);
}
MaterialInstance configuredMaterialInstance = materialRepository.findOrCreateFrom(material);
if (!configuredMaterialInstance.equals(modificationsMaterialInstance)) {
throw new RuntimeException(
"Please fix the revision order to match material configuration order. Revision given for: " + modificationsMaterialInstance + " against configured material: " + configuredMaterialInstance);
}
if (oldInstance == null) {
buildCause.addRevision(new MaterialRevision(material, modifications));
} else {
MaterialRevision oldRev = oldInstance.getBuildCause().getMaterialRevisions().findRevisionFor(material);
List<Modification> modificationsSince = materialRepository.findModificationsSince(material, oldRev);
List<Modification> newRange = new ArrayList<>();
for (Modification newMod : modificationsSince) {
newRange.add(0, newMod);
if (newMod.equals(modifications)) {
break;
}
}
buildCause.addRevision(new MaterialRevision(material, newRange));
}
}
return dbHelper.schedulePipelineWithAllStages(pipeline.config, BuildCause.createWithModifications(buildCause, "loser"));
}
private List<Modification> modForRev(final RevisionsForMaterial revision) {
return (List<Modification>) materialRepository.getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException, SQLException {
Query q = session.createQuery("from Modification where revision in (:in) order by id desc");
q.setParameterList("in", revision.revs);
List list = q.list();
if (list.isEmpty()) {
throw new RuntimeException("you are trying to load revision " + revision + " which doesn't exist");
}
return list;
}
});
}
public Pipeline scheduleWith(AddedPipeline pipeline, String... revisions) {
RevisionsForMaterial[] revsForMat = new RevisionsForMaterial[revisions.length];
for (int i = 0; i < revisions.length; i++) {
revsForMat[i] = rs(revisions[i])[0];
}
return scheduleWith(pipeline, revsForMat);
}
public String runAndFail(final AddedPipeline pipeline, String... revisions) {
return runAndFail(pipeline, date, revisions);
}
private Modification modForRev(final String revision) {
return (Modification) materialRepository.getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException, SQLException {
Query q = session.createQuery("from Modification where revision = ?");
q.setParameter(0, revision);
List list = q.list();
if (list.isEmpty()) {
throw new RuntimeException("you are trying to load revision " + revision + " which doesn't exist");
}
return list.get(0);
}
});
}
public static final class AddedPipeline {
public final PipelineConfig config;
public final DependencyMaterial material;
public AddedPipeline(PipelineConfig config, DependencyMaterial material) {
this.config = config;
this.material = material;
}
public MaterialConfig materialConfig() {
return material.config();
}
}
public static final class MaterialDeclaration {
final Material material;
final String dest;
private MaterialDeclaration(Material material) {
this(material, null);
}
public MaterialDeclaration(Material material, String dest) {
this.material = material;
this.dest = dest;
}
public MaterialConfig materialConfig() {
return material.config();
}
}
public MaterialDeclaration m(Material material, String dest) {
return new MaterialDeclaration(material, dest);
}
public MaterialDeclaration m(Material material) {
return new MaterialDeclaration(material);
}
public MaterialDeclaration m(AddedPipeline pipeline) {
return m(pipeline.material);
}
public MaterialDeclaration m(AddedPipeline pipeline, String materialName) {
return m(new DependencyMaterial(new CaseInsensitiveString(materialName), pipeline.material.getPipelineName(), pipeline.material.getStageName()));
}
public AddedPipeline saveConfigWithTimer(String pipelineName, TimerConfig timer, MaterialDeclaration... materialDeclaration) {
String stageName = AutoTriggerDependencyResolutionTest.STAGE_NAME;
MaterialConfigs materialConfigs = new MaterialConfigs();
for (MaterialDeclaration mDecl : materialDeclaration) {
MaterialConfig materialConfig = AutoTriggerDependencyResolutionTest.CLONER.deepClone(mDecl.material.config());
materialConfigs.add(materialConfig);
}
PipelineConfig cfg = configHelper.addPipelineWithGroupAndTimer(DEFAULT_GROUP, pipelineName, materialConfigs, stageName, timer, "job1");
return new AddedPipeline(cfg, new DependencyMaterial(str(pipelineName), str(stageName)));
}
public AddedPipeline saveConfigWithGroup(String groupName, String pipelineName, MaterialDeclaration... materialDecls) {
return saveConfigWith(groupName, pipelineName, AutoTriggerDependencyResolutionTest.STAGE_NAME, materialDecls);
}
public AddedPipeline saveConfigWith(String pipelineName, String stageName, MaterialDeclaration... materialDecls) {
return saveConfigWith(DEFAULT_GROUP, pipelineName, stageName, materialDecls);
}
public AddedPipeline saveConfigWith(String pipelineName, String stageName, MaterialDeclaration materialDeclaration, String[] builds) {
MaterialConfigs materialConfigs = new MaterialConfigs();
MaterialConfig materialConfig = AutoTriggerDependencyResolutionTest.CLONER.deepClone(materialDeclaration.material.config());
materialConfigs.add(materialConfig);
PipelineConfig cfg = configHelper.addPipelineWithGroup(DEFAULT_GROUP, pipelineName, materialConfigs, stageName, builds);
return new AddedPipeline(cfg, new DependencyMaterial(str(pipelineName), str(stageName)));
}
public void addPackageDefinition(PackageMaterialConfig pkgMaterialConfig) {
configHelper.addPackageDefinition(pkgMaterialConfig);
}
public void addSCMConfig(SCM scmConfig) {
configHelper.addSCMConfig(scmConfig);
}
public AddedPipeline saveConfigWith(String pipelineName, MaterialDeclaration... materialDecls) {
return saveConfigWith(DEFAULT_GROUP, pipelineName, AutoTriggerDependencyResolutionTest.STAGE_NAME, materialDecls);
}
private AddedPipeline saveConfigWith(String groupName, String pipelineName, String stageName, MaterialDeclaration... materialDecls) {
MaterialConfigs materialConfigs = new MaterialConfigs();
for (MaterialDeclaration mDecl : materialDecls) {
MaterialConfig materialConfig = AutoTriggerDependencyResolutionTest.CLONER.deepClone(mDecl.material.config());
materialConfigs.add(materialConfig);
}
PipelineConfig cfg = configHelper.addPipelineWithGroup(groupName, pipelineName, materialConfigs, stageName, "job1");
return new AddedPipeline(cfg, new DependencyMaterial(str(pipelineName), str(stageName)));
}
public AddedPipeline changeStagenameForToPipeline(String pipelineName, String oldStageName, String newStageName) {
PipelineConfig cfg = configHelper.changeStagenameForToPipeline(pipelineName, oldStageName, newStageName);
return new AddedPipeline(cfg, new DependencyMaterial(str(pipelineName), str(newStageName)));
}
public AddedPipeline addStageToPipeline(CaseInsensitiveString pipelineName, String stageName) throws Exception {
PipelineConfig config = configHelper.addStageToPipeline(pipelineName.toString(), stageName);
return new AddedPipeline(config, new DependencyMaterial(pipelineName, new CaseInsensitiveString(stageName)));
}
public AddedPipeline addMaterialToPipeline(AddedPipeline pipeline, MaterialDeclaration mDecl) {
MaterialConfig materialConfig = AutoTriggerDependencyResolutionTest.CLONER.deepClone(mDecl.materialConfig());
PipelineConfig cfg = configHelper.addMaterialToPipeline(pipeline.config.name().toString(), materialConfig);
return new AddedPipeline(cfg, pipeline.material);
}
public AddedPipeline removeMaterialFromPipeline(AddedPipeline pipeline, MaterialDeclaration mDecl) {
MaterialConfig materialConfig = AutoTriggerDependencyResolutionTest.CLONER.deepClone(mDecl.materialConfig());
PipelineConfig cfg = configHelper.removeMaterialFromPipeline(pipeline.config.name().toString(), materialConfig);
return new AddedPipeline(cfg, pipeline.material);
}
public void checkinInOrder(final Material material, final String... revisions) {
checkinInOrder(material, date, revisions);
}
public void checkinInOrder(final Material material, final Date dateOfCheckin, final String... revisions) {
transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
for (int i = 0; i < revisions.length; i++) {
String revision = revisions[i];
materialRepository.saveMaterialRevision(new MaterialRevision(material,
new Modification("loser number " + i, "commit " + i, "e" + i + "@mail", new DateTime(dateOfCheckin.getTime()).plusHours(i).toDate(), revision)));
}
return null;
}
});
}
public void checkinFile(final Material material, final String revision, final File file, final ModifiedAction modifiedAction) {
checkinFiles(material, revision, a(file), modifiedAction);
}
public void checkinFiles(final Material material, final String revision, final List<File> files, final ModifiedAction modifiedAction) {
transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
Modification modification = new Modification("user", "comment", "a@b.com", date, revision);
for (File file : files) {
modification.createModifiedFile(file.getName(), file.getParent(), modifiedAction);
}
materialRepository.saveMaterialRevision(new MaterialRevision(material, modification));
return null;
}
});
}
public void checkinFiles(final Material material, final String revision, final List<File> files, final ModifiedAction modifiedAction,final Date date) {
transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
Modification modification = new Modification("user", "comment", "a@b.com", date, revision);
for (File file : files) {
modification.createModifiedFile(file.getName(), file.getParent(), modifiedAction);
}
materialRepository.saveMaterialRevision(new MaterialRevision(material, modification));
return null;
}
});
}
}