/* * 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.domain; import com.rits.cloning.Cloner; import com.thoughtworks.go.config.*; import com.thoughtworks.go.config.materials.MaterialConfigs; import com.thoughtworks.go.config.materials.PackageMaterialConfig; import com.thoughtworks.go.config.materials.ScmMaterialConfig; import com.thoughtworks.go.config.materials.dependency.DependencyMaterialConfig; import com.thoughtworks.go.config.materials.git.GitMaterialConfig; import com.thoughtworks.go.config.materials.perforce.P4MaterialConfig; import com.thoughtworks.go.domain.packagerepository.PackageDefinitionMother; import com.thoughtworks.go.helper.GoConfigMother; import com.thoughtworks.go.helper.MaterialConfigsMother; import org.hamcrest.CoreMatchers; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Matchers; import java.util.Arrays; import java.util.List; import static com.thoughtworks.go.util.TestUtils.contains; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.nullValue; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.*; public class PipelineConfigValidationTest { private CruiseConfig config; private PipelineConfig pipeline; private GoConfigMother goConfigMother; private ValidationContext validationContext; @Before public void setup() { config = GoConfigMother.configWithPipelines("pipeline1", "pipeline2", "pipeline3", "go"); pipeline = config.pipelineConfigByName(new CaseInsensitiveString("go")); goConfigMother = new GoConfigMother(); } @Test public void shouldEnsureStageNameUniqueness() { CruiseConfig cruiseConfig = new BasicCruiseConfig(); PipelineConfig pipelineConfig = goConfigMother.addPipeline(cruiseConfig, "pipeline1", "stage", "build"); JobConfig jobConfig = new JobConfig("my-build"); jobConfig.addTask(new ExecTask("ls", "-la", "tmp")); StageConfig stageConfig = new StageConfig(new CaseInsensitiveString("stage"), new JobConfigs(jobConfig)); pipelineConfig.addStageWithoutValidityAssertion(stageConfig); pipelineConfig.validate(validationContext); assertThat(stageConfig.errors().getAllOn("name"), is(Arrays.asList("You have defined multiple stages called 'stage'. Stage names are case-insensitive and must be unique."))); assertThat(pipelineConfig.get(0).errors().getAllOn("name"), is(Arrays.asList("You have defined multiple stages called 'stage'. Stage names are case-insensitive and must be unique."))); assertThat(cruiseConfig.validateAfterPreprocess().get(0).getAllOn("name"), is(Arrays.asList("You have defined multiple stages called 'stage'. Stage names are case-insensitive and must be unique."))); } @Test public void isValid_shouldEnsureLabelTemplateRefersToValidMaterials() { pipeline.setLabelTemplate("pipeline-${COUNT}-${myGit}"); pipeline.validate(validationContext); ConfigErrors configErrors = pipeline.errors(); List<String> errors = configErrors.getAllOn("labelTemplate"); assertThat(errors.size(), is(1)); assertThat(errors, hasItem("You have defined a label template in pipeline go that refers to a material called myGit, but no material with this name is defined.")); } @Test public void isValid_shouldEnsureLabelTemplateRefersToAMaterialOrCOUNT() { pipeline.setLabelTemplate("label-template-without-material-or-count"); pipeline.validate(validationContext); ConfigErrors configErrors = pipeline.errors(); List<String> errors = configErrors.getAllOn("labelTemplate"); assertThat(errors.size(), is(1)); assertThat(errors.get(0), startsWith("Invalid label")); } @Test public void isValid_shouldEnsureLabelTemplateHasValidVariablePattern() { pipeline.setLabelTemplate("pipeline-${COUNT"); pipeline.validate(validationContext); ConfigErrors configErrors = pipeline.errors(); assertThat(configErrors.isEmpty(), is(false)); List<String> errors = configErrors.getAllOn("labelTemplate"); assertThat(errors.size(), is(1)); assertThat(errors.get(0), startsWith("Invalid label")); } @Test public void isValid_labelTemplateShouldAcceptLabelTemplateWithHashCharacter() { pipeline.setLabelTemplate("foo${COUNT}-tanker#"); pipeline.validate(validationContext); ConfigErrors configErrors = pipeline.errors(); assertThat(configErrors.isEmpty(), is(true)); List<String> errors = configErrors.getAllOn("labelTemplate"); assertThat(errors, is(nullValue())); } @Test public void isValid_shouldMatchMaterialNamesInACaseInsensitiveManner() { pipeline.setLabelTemplate("pipeline-${count}-${myGit}"); ScmMaterialConfig gitMaterialConfig = MaterialConfigsMother.gitMaterialConfig("git://url"); gitMaterialConfig.setName(new CaseInsensitiveString("mygit")); pipeline.addMaterialConfig(gitMaterialConfig); pipeline.validate(validationContext); assertThat(pipeline.errors().isEmpty(), is(true)); List<String> errors = pipeline.errors().getAllOn("labelTemplate"); assertThat(errors, is(nullValue())); } @Test public void isValid_shouldEnsureReturnsTrueWhenLabelTemplateRefersToValidMaterials() { pipeline.setLabelTemplate("pipeline-${COUNT}-${myGit}"); GitMaterialConfig gitConfig = MaterialConfigsMother.gitMaterialConfig("git://url"); gitConfig.setName(new CaseInsensitiveString("myGit")); pipeline.addMaterialConfig(gitConfig); pipeline.validate(validationContext); assertThat(pipeline.errors().isEmpty(), is(true)); List<String> errors = pipeline.errors().getAllOn("labelTemplate"); assertThat(errors, is(nullValue())); } @Test public void isValid_shouldAllowColonForLabelTemplate() throws Exception { pipeline.setLabelTemplate("pipeline-${COUNT}-${repo:name}"); pipeline.addMaterialConfig(new PackageMaterialConfig(new CaseInsensitiveString("repo:name"), "package-id", PackageDefinitionMother.create("package-id"))); pipeline.validate(validationContext); assertThat(pipeline.errors().getAllOn("labelTemplate"), is(nullValue())); } @Test public void validate_shouldEnsureThatTemplateFollowsTheNameType() { PipelineConfig pipelineConfig = new PipelineConfig(new CaseInsensitiveString("name"), new MaterialConfigs()); pipelineConfig.setTemplateName(new CaseInsensitiveString(".Name")); config.addPipeline("group",pipelineConfig); pipelineConfig.validateTemplate(new PipelineTemplateConfig()); assertThat(pipelineConfig.errors().isEmpty(), is(false)); assertThat(pipelineConfig.errors().on(PipelineConfig.TEMPLATE_NAME), is("Invalid template name '.Name'. This must be alphanumeric and can contain underscores and periods (however, it cannot start with a period). The maximum allowed length is 255 characters.")); } @Test public void validate_shouldEnsureThatPipelineFollowsTheNameType() { PipelineConfig config = new PipelineConfig(new CaseInsensitiveString(".name"), new MaterialConfigs()); config.validate(validationContext); assertThat(config.errors().isEmpty(), is(false)); assertThat(config.errors().on(PipelineConfig.NAME), is("Invalid pipeline name '.name'. This must be alphanumeric and can contain underscores and periods (however, it cannot start with a period). The maximum allowed length is 255 characters.")); } @Test public void shouldBeValidIfTheReferencedPipelineExists() throws Exception { pipeline.addMaterialConfig(new DependencyMaterialConfig(new CaseInsensitiveString("pipeline2"), new CaseInsensitiveString("stage"))); pipeline.validate(validationContext); assertThat(pipeline.errors().isEmpty(), is(true)); } @Test public void shouldAllowMultipleDependenciesForDifferentPipelines() throws Exception { pipeline.addMaterialConfig(new DependencyMaterialConfig(new CaseInsensitiveString("pipeline2"), new CaseInsensitiveString("stage"))); pipeline.addMaterialConfig(new DependencyMaterialConfig(new CaseInsensitiveString("pipeline3"), new CaseInsensitiveString("stage"))); pipeline.validate(validationContext); assertThat(pipeline.errors().isEmpty(), is(true)); } @Test public void shouldAllowDependenciesFromMultiplePipelinesToTheSamePipeline() throws Exception { pipeline.addMaterialConfig(new DependencyMaterialConfig(new CaseInsensitiveString("pipeline2"), new CaseInsensitiveString("stage"))); PipelineConfig pipeline3 = config.pipelineConfigByName(new CaseInsensitiveString("pipeline3")); pipeline3.addMaterialConfig(new DependencyMaterialConfig(new CaseInsensitiveString("pipeline2"), new CaseInsensitiveString("stage"))); pipeline.validate(validationContext); assertThat(pipeline.errors().isEmpty(), is(true)); pipeline3.validate(validationContext); assertThat(pipeline3.errors().isEmpty(), is(true)); } @Test public void shouldNotAllowAnEmptyView() throws Exception { CruiseConfig config = GoConfigMother.configWithPipelines("pipeline1"); P4MaterialConfig materialConfig = new P4MaterialConfig("localhost:1666", ""); PipelineConfig pipelineConfig = config.pipelineConfigByName(new CaseInsensitiveString("pipeline1")); pipelineConfig.addMaterialConfig(materialConfig); materialConfig.validate(validationContext); assertThat(materialConfig.errors().on("view"), is("P4 view cannot be empty.")); } @Test public void shouldValidateAndUpdatePipelineConfig() { PipelineConfig pipeline = new PipelineConfig(); pipeline.setName("validPipeline"); pipeline.setMaterialConfigs(new MaterialConfigs(MaterialConfigsMother.gitMaterialConfig(), MaterialConfigsMother.svnMaterialConfig())); StageConfig stage1 = getStageConfig("stage1", "s1j1"); StageConfig stage2 = getStageConfig("stage2", "s2j1"); pipeline.getStages().add(stage1); pipeline.getStages().add(stage2); BasicCruiseConfig cruiseConfig = new BasicCruiseConfig(new BasicPipelineConfigs("group", new Authorization(), pipeline)); boolean isValid = pipeline.validateTree(PipelineConfigSaveValidationContext.forChain(true, cruiseConfig.getGroups().first().getGroup(), cruiseConfig, pipeline)); assertThat(isValid, is(true)); assertThat(pipeline.materialConfigs().errors().isEmpty(), is(true)); assertThat(pipeline.materialConfigs().get(0).errors().isEmpty(), is(true)); assertThat(pipeline.materialConfigs().get(1).errors().isEmpty(), is(true)); assertThat(pipeline.errors().getAll().isEmpty(), is(true)); } @Test public void shouldHandleNullStageNamesWhileValidating() { StageConfig s1 = new StageConfig(); StageConfig s2 = new StageConfig(new CaseInsensitiveString("s2"), new JobConfigs()); PipelineConfig pipeline = new PipelineConfig(new CaseInsensitiveString("p1"), new MaterialConfigs(), s1, s2); pipeline.validate(null); assertThat(s1.errors().on(StageConfig.NAME).contains("Invalid stage name 'null'"), is(true)); } @Test public void shouldValidateTree() { PipelineConfig pipeline = new PipelineConfig(); pipeline.setName("pipeline"); pipeline.addEnvironmentVariable("", ""); pipeline.addParam(new ParamConfig("", "")); pipeline.setMaterialConfigs(new MaterialConfigs(MaterialConfigsMother.gitMaterialConfig(), MaterialConfigsMother.svnMaterialConfig())); StageConfig stage1 = getStageConfig("stage1", "s1j1"); StageConfig stage2 = getStageConfig("stage2", "s2j1"); pipeline.getStages().add(stage1); pipeline.getStages().add(stage2); BasicCruiseConfig cruiseConfig = new BasicCruiseConfig(new BasicPipelineConfigs("group", new Authorization(), pipeline)); boolean isValid = pipeline.validateTree(PipelineConfigSaveValidationContext.forChain(true, cruiseConfig.getGroups().first().getGroup(), cruiseConfig, pipeline)); assertThat(isValid, is(false)); assertThat(pipeline.getVariables().get(0).errors().firstError(), is("Environment Variable cannot have an empty name for pipeline 'pipeline'.")); assertThat(pipeline.getParams().get(0).errors().firstError(), is("Parameter cannot have an empty name for pipeline 'pipeline'.")); assertThat(pipeline.materialConfigs().errors().isEmpty(), is(true)); assertThat(pipeline.materialConfigs().get(0).errors().isEmpty(), is(true)); assertThat(pipeline.materialConfigs().get(1).errors().isEmpty(), is(true)); assertThat(pipeline.errors().getAll().isEmpty(), is(true)); } @Test public void shouldFailValidateWhenUpstreamPipelineForDependencyMaterailDoesNotExist() { String upstreamPipeline = "non-existant"; PipelineConfig pipelineConfig = GoConfigMother.createPipelineConfigWithMaterialConfig( new DependencyMaterialConfig(new CaseInsensitiveString(upstreamPipeline), new CaseInsensitiveString("non-existant"))); BasicCruiseConfig cruiseConfig = new BasicCruiseConfig(new BasicPipelineConfigs(pipelineConfig)); boolean isValid = pipelineConfig.validateTree(PipelineConfigSaveValidationContext.forChain(true, cruiseConfig.getGroups().first().getGroup(), cruiseConfig, pipelineConfig)); assertThat(isValid, is(false)); ConfigErrors materialErrors = pipelineConfig.materialConfigs().first().errors(); assertThat(materialErrors.isEmpty(), is(false)); assertThat(materialErrors.firstError(), is("Pipeline with name 'non-existant' does not exist, it is defined as a dependency for pipeline 'pipeline' (cruise-config.xml)")); } @Test public void shouldFailValidateWhenUpstreamStageForDependencyMaterailDoesNotExist() { String upstreamPipeline = "upstream"; String upstreamStage = "non-existant"; PipelineConfig upstream = GoConfigMother.createPipelineConfigWithMaterialConfig(upstreamPipeline, new GitMaterialConfig("url")); PipelineConfig pipelineConfig = GoConfigMother.createPipelineConfigWithMaterialConfig("downstream", new DependencyMaterialConfig(new CaseInsensitiveString(upstreamPipeline), new CaseInsensitiveString(upstreamStage))); BasicCruiseConfig cruiseConfig = new BasicCruiseConfig(new BasicPipelineConfigs(pipelineConfig, upstream)); boolean isValid = pipelineConfig.validateTree(PipelineConfigSaveValidationContext.forChain(true, cruiseConfig.getGroups().first().getGroup(), cruiseConfig, pipelineConfig)); assertThat(isValid, is(false)); ConfigErrors materialErrors = pipelineConfig.materialConfigs().first().errors(); assertThat(materialErrors.isEmpty(), is(false)); assertThat(materialErrors.firstError(), is("Stage with name 'non-existant' does not exist on pipeline 'upstream', it is being referred to from pipeline 'downstream' (cruise-config.xml)")); } @Test public void shouldReturnTrueIfAllDescendentsAreValid() { StageConfig stageConfig = mock(StageConfig.class); MaterialConfigs materialConfigs = mock(MaterialConfigs.class); when(materialConfigs.iterator()).thenReturn(new MaterialConfigs().iterator()); ParamsConfig paramsConfig = mock(ParamsConfig.class); EnvironmentVariablesConfig variables = mock(EnvironmentVariablesConfig.class); TrackingTool trackingTool = mock(TrackingTool.class); MingleConfig mingleConfig = mock(MingleConfig.class); TimerConfig timerConfig = mock(TimerConfig.class); when(stageConfig.validateTree(Matchers.<PipelineConfigSaveValidationContext>any())).thenReturn(true); when(materialConfigs.validateTree(Matchers.<PipelineConfigSaveValidationContext>any())).thenReturn(true); when(paramsConfig.validateTree(Matchers.<PipelineConfigSaveValidationContext>any())).thenReturn(true); when(variables.validateTree(Matchers.<PipelineConfigSaveValidationContext>any())).thenReturn(true); when(trackingTool.validateTree(Matchers.<PipelineConfigSaveValidationContext>any())).thenReturn(true); when(mingleConfig.validateTree(Matchers.<PipelineConfigSaveValidationContext>any())).thenReturn(true); when(timerConfig.validateTree(Matchers.<PipelineConfigSaveValidationContext>any())).thenReturn(true); PipelineConfig pipelineConfig = new PipelineConfig(new CaseInsensitiveString("p1"), materialConfigs, stageConfig); pipelineConfig.setParams(paramsConfig); pipelineConfig.setVariables(variables); pipelineConfig.setTrackingTool(trackingTool); pipelineConfig.setMingleConfig(mingleConfig); pipelineConfig.setTimer(timerConfig); boolean isValid = pipelineConfig.validateTree(PipelineConfigSaveValidationContext.forChain(true, "group", new BasicCruiseConfig(new BasicPipelineConfigs("group", new Authorization())), pipelineConfig)); assertTrue(isValid); verify(stageConfig).validateTree(Matchers.<PipelineConfigSaveValidationContext>any()); verify(materialConfigs, atLeastOnce()).iterator(); verify(materialConfigs).validateTree(Matchers.<PipelineConfigSaveValidationContext>any()); verify(paramsConfig).validateTree(Matchers.<PipelineConfigSaveValidationContext>any()); verify(variables).validateTree(Matchers.<PipelineConfigSaveValidationContext>any()); verify(trackingTool).validateTree(Matchers.<PipelineConfigSaveValidationContext>any()); verify(mingleConfig).validateTree(Matchers.<PipelineConfigSaveValidationContext>any()); verify(timerConfig).validateTree(Matchers.<PipelineConfigSaveValidationContext>any()); } @Test public void shouldReturnFalseIfAnyDescendentIsInValid() { StageConfig stageConfig = mock(StageConfig.class); MaterialConfigs materialConfigs = mock(MaterialConfigs.class); when(materialConfigs.iterator()).thenReturn(new MaterialConfigs().iterator()); ParamsConfig paramsConfig = mock(ParamsConfig.class); EnvironmentVariablesConfig variables = mock(EnvironmentVariablesConfig.class); TrackingTool trackingTool = mock(TrackingTool.class); MingleConfig mingleConfig = mock(MingleConfig.class); TimerConfig timerConfig = mock(TimerConfig.class); when(stageConfig.validateTree(Matchers.<PipelineConfigSaveValidationContext>any())).thenReturn(false); when(materialConfigs.validateTree(Matchers.<PipelineConfigSaveValidationContext>any())).thenReturn(false); when(paramsConfig.validateTree(Matchers.<PipelineConfigSaveValidationContext>any())).thenReturn(false); when(variables.validateTree(Matchers.<PipelineConfigSaveValidationContext>any())).thenReturn(false); when(trackingTool.validateTree(Matchers.<PipelineConfigSaveValidationContext>any())).thenReturn(false); when(mingleConfig.validateTree(Matchers.<PipelineConfigSaveValidationContext>any())).thenReturn(false); when(timerConfig.validateTree(Matchers.<PipelineConfigSaveValidationContext>any())).thenReturn(false); PipelineConfig pipelineConfig = new PipelineConfig(new CaseInsensitiveString("p1"), materialConfigs, stageConfig); pipelineConfig.setParams(paramsConfig); pipelineConfig.setVariables(variables); pipelineConfig.setTrackingTool(trackingTool); pipelineConfig.setMingleConfig(mingleConfig); pipelineConfig.setTimer(timerConfig); boolean isValid = pipelineConfig.validateTree(PipelineConfigSaveValidationContext.forChain(true, "group", new BasicCruiseConfig(new BasicPipelineConfigs("group", new Authorization())), pipelineConfig)); assertFalse(isValid); verify(stageConfig).validateTree(Matchers.<PipelineConfigSaveValidationContext>any()); verify(materialConfigs).validateTree(Matchers.<PipelineConfigSaveValidationContext>any()); verify(paramsConfig).validateTree(Matchers.<PipelineConfigSaveValidationContext>any()); verify(variables).validateTree(Matchers.<PipelineConfigSaveValidationContext>any()); verify(trackingTool).validateTree(Matchers.<PipelineConfigSaveValidationContext>any()); verify(mingleConfig).validateTree(Matchers.<PipelineConfigSaveValidationContext>any()); verify(timerConfig).validateTree(Matchers.<PipelineConfigSaveValidationContext>any()); } @Test public void shouldValidateAPipelineHasAtleastOneStage() { PipelineConfig pipelineConfig = new PipelineConfig(new CaseInsensitiveString("p"), new MaterialConfigs()); pipelineConfig.validateTree(PipelineConfigSaveValidationContext.forChain(true, "group", new BasicCruiseConfig(new BasicPipelineConfigs("group", new Authorization())), pipelineConfig)); assertThat(pipelineConfig.errors().on("pipeline"), is("Pipeline 'p' does not have any stages configured. A pipeline must have at least one stage.")); } @Test public void shouldDetectCyclicDependencies() { String pipelineName = "p1"; BasicCruiseConfig cruiseConfig = GoConfigMother.configWithPipelines(pipelineName, "p2", "p3"); PipelineConfig p2 = cruiseConfig.getPipelineConfigByName(new CaseInsensitiveString("p2")); p2.addMaterialConfig(new DependencyMaterialConfig(new CaseInsensitiveString(pipelineName), new CaseInsensitiveString("stage"))); PipelineConfig p3 = cruiseConfig.getPipelineConfigByName(new CaseInsensitiveString("p3")); p3.addMaterialConfig(new DependencyMaterialConfig(new CaseInsensitiveString("p2"), new CaseInsensitiveString("stage"))); PipelineConfig p1 = cruiseConfig.getPipelineConfigByName(new CaseInsensitiveString(pipelineName)); p1 = new Cloner().deepClone(p1); // Do not remove cloning else it changes the underlying cache object defeating the purpose of the test. p1.addMaterialConfig(new DependencyMaterialConfig(new CaseInsensitiveString("p3"), new CaseInsensitiveString("stage"))); p1.validateTree(PipelineConfigSaveValidationContext.forChain(true, cruiseConfig.getGroups().first().getGroup(), cruiseConfig, p1)); assertThat(p1.materialConfigs().errors().isEmpty(), is(false)); assertThat(p1.materialConfigs().errors().on("base"), is("Circular dependency: p1 <- p2 <- p3 <- p1")); } @Test public void shouldValidateThatPipelineAssociatedToATemplateDoesNotHaveStagesDefinedLocally() { PipelineConfig pipelineConfig = new PipelineConfig(new CaseInsensitiveString("wunderbar"), new MaterialConfigs()); config.addPipeline("g", pipelineConfig); pipelineConfig.setTemplateName(new CaseInsensitiveString("template-name")); pipelineConfig.addStageWithoutValidityAssertion(new StageConfig(new CaseInsensitiveString("stage"), new JobConfigs())); pipelineConfig.validateTemplate(null); assertThat(pipelineConfig.errors().on("stages"), is("Cannot add stages to pipeline 'wunderbar' which already references template 'template-name'")); assertThat(pipelineConfig.errors().on("template"), is("Cannot set template 'template-name' on pipeline 'wunderbar' because it already has stages defined")); } @Test public void shouldAddValidationErrorWhenAssociatedTemplateDoesNotExist() { PipelineConfig pipelineConfig = new PipelineConfig(new CaseInsensitiveString("wunderbar"), new MaterialConfigs()); config.addPipeline("group", pipelineConfig); pipelineConfig.setTemplateName(new CaseInsensitiveString("does-not-exist")); pipelineConfig.validateTemplate(null); assertThat(pipelineConfig.errors().on("pipeline"), is("Pipeline 'wunderbar' refers to non-existent template 'does-not-exist'.")); } @Test public void shouldNotAddValidationErrorWhenAssociatedTemplateExists() { PipelineConfig pipelineConfig = new PipelineConfig(new CaseInsensitiveString("wunderbar"), new MaterialConfigs()); config.addPipeline("group", pipelineConfig); config.addTemplate(new PipelineTemplateConfig(new CaseInsensitiveString("t1"))); pipelineConfig.setTemplateName(new CaseInsensitiveString("t1")); pipelineConfig.validateTree(PipelineConfigSaveValidationContext.forChain(true, "group", config, pipelineConfig)); assertThat(pipelineConfig.errors().getAllOn("template"), is(CoreMatchers.nullValue())); } @Test public void shouldFailValidationIfAStageIsDeletedWhileItsStillReferredToByADownstreamPipeline() { BasicCruiseConfig cruiseConfig = GoConfigMother.configWithPipelines("p1", "p2"); PipelineConfig p1 = cruiseConfig.getPipelineConfigByName(new CaseInsensitiveString("p1")); PipelineConfig p2 = cruiseConfig.getPipelineConfigByName(new CaseInsensitiveString("p2")); p2.addMaterialConfig(new DependencyMaterialConfig(p1.name(), p1.first().name())); String group = cruiseConfig.getGroups().first().getGroup(); StageConfig stageConfig = new StageConfig(new CaseInsensitiveString("s1"), new JobConfigs(new JobConfig(new CaseInsensitiveString("j1")))); PipelineConfig pipelineConfig = new PipelineConfig(p1.name(), new MaterialConfigs(), stageConfig); cruiseConfig.update(group, pipelineConfig.name().toString(), pipelineConfig); PipelineConfigSaveValidationContext validationContext = PipelineConfigSaveValidationContext.forChain(false, group, cruiseConfig, pipelineConfig); pipelineConfig.validateTree(validationContext); assertThat(pipelineConfig.errors().on("base"), is("Stage with name 'stage' does not exist on pipeline 'p1', it is being referred to from pipeline 'p2' (cruise-config.xml)")); } @Test public void shouldFailValidationIfAJobIsDeletedWhileItsStillReferredToByADescendentPipelineThroughFetchArtifact() { BasicCruiseConfig cruiseConfig = GoConfigMother.configWithPipelines("p1", "p2", "p3"); PipelineConfig p1 = cruiseConfig.getPipelineConfigByName(new CaseInsensitiveString("p1")); PipelineConfig p2 = cruiseConfig.getPipelineConfigByName(new CaseInsensitiveString("p2")); PipelineConfig p3 = cruiseConfig.getPipelineConfigByName(new CaseInsensitiveString("p3")); p2.addMaterialConfig(new DependencyMaterialConfig(p1.name(), p1.first().name())); JobConfig p2S2J2 = new JobConfig(new CaseInsensitiveString("j2")); p2S2J2.addTask(fetchTaskFromSamePipeline(p2)); p2.add(new StageConfig(new CaseInsensitiveString("stage2"),new JobConfigs(p2S2J2) )); p3.addMaterialConfig(new DependencyMaterialConfig(p2.name(), p2.first().name())); p3.first().getJobs().first().addTask(new FetchTask(new CaseInsensitiveString("p1/p2"), p1.first().name(), p1.first().getJobs().first().name(), "src", "dest")); StageConfig stageConfig = new StageConfig(new CaseInsensitiveString("stage"), new JobConfigs(new JobConfig(new CaseInsensitiveString("new-job")))); PipelineConfig pipelineConfig = new PipelineConfig(p1.name(), new MaterialConfigs(), stageConfig); String group = cruiseConfig.getGroups().first().getGroup(); cruiseConfig.update(group, pipelineConfig.name().toString(), pipelineConfig); PipelineConfigSaveValidationContext validationContext = PipelineConfigSaveValidationContext.forChain(false, group, cruiseConfig, pipelineConfig); pipelineConfig.validateTree(validationContext); assertThat(pipelineConfig.errors().on("base"), is("\"p3 :: stage :: job\" tries to fetch artifact from job \"p1 :: stage :: job\" which does not exist.")); } private FetchTask fetchTaskFromSamePipeline(PipelineConfig pipelineConfig) { FetchTask fetchTask = new FetchTask(); fetchTask.setStage(pipelineConfig.first().name()); fetchTask.setJob(pipelineConfig.first().getJobs().first().name()); return fetchTask; } @Test public void shouldAddValidationErrorsFromStagesOntoPipelineIfPipelineIsAssociatedToATemplate() { BasicCruiseConfig cruiseConfig = GoConfigMother.configWithPipelines("p1", "p2", "p3"); PipelineConfig p1 = cruiseConfig.getPipelineConfigByName(new CaseInsensitiveString("p1")); PipelineConfig p2 = cruiseConfig.getPipelineConfigByName(new CaseInsensitiveString("p2")); PipelineConfig p3 = cruiseConfig.getPipelineConfigByName(new CaseInsensitiveString("p3")); p2.addMaterialConfig(new DependencyMaterialConfig(p1.name(), p1.first().name())); p3.addMaterialConfig(new DependencyMaterialConfig(p2.name(), p2.first().name())); p3.first().getJobs().first().addTask(new FetchTask(new CaseInsensitiveString("p1/p2"), p1.first().name(), p1.first().getJobs().first().name(), "src", "dest")); StageConfig stageConfig = new StageConfig(new CaseInsensitiveString("stage"), new JobConfigs(new JobConfig(new CaseInsensitiveString("new-job")))); PipelineConfig pipelineConfig = new PipelineConfig(p1.name(), new MaterialConfigs(), stageConfig); String group = cruiseConfig.getGroups().first().getGroup(); cruiseConfig.update(group, pipelineConfig.name().toString(), pipelineConfig); PipelineConfigSaveValidationContext validationContext = PipelineConfigSaveValidationContext.forChain(false, group, cruiseConfig, pipelineConfig); pipelineConfig.validateTree(validationContext); assertThat(pipelineConfig.errors().on("base"), is("\"p3 :: stage :: job\" tries to fetch artifact from job \"p1 :: stage :: job\" which does not exist.")); } @Test public void shouldCheckForPipelineNameUniqueness(){ BasicCruiseConfig cruiseConfig = GoConfigMother.configWithPipelines("p1"); String group = "group"; cruiseConfig.getGroups().add(new BasicPipelineConfigs(group, new Authorization())); PipelineConfig p1Duplicate = GoConfigMother.createPipelineConfigWithMaterialConfig("p1", new GitMaterialConfig("url")); cruiseConfig.addPipeline(group, p1Duplicate); PipelineConfigSaveValidationContext context = PipelineConfigSaveValidationContext.forChain(true, group, cruiseConfig, p1Duplicate); p1Duplicate.validateTree(context); assertThat(p1Duplicate.errors().on(PipelineConfig.NAME), is(String.format("You have defined multiple pipelines named '%s'. Pipeline names must be unique. Source(s): [cruise-config.xml]", p1Duplicate.name()))); } @Test public void shouldValidateGroupNameWhenPipelineIsBeingCreatedUnderANonExistantGroup() { BasicCruiseConfig cruiseConfig = GoConfigMother.configWithPipelines("p1"); PipelineConfig p1 = cruiseConfig.getPipelineConfigByName(new CaseInsensitiveString("p1")); String groupName = "%$-with-invalid-characters"; cruiseConfig.addPipeline(groupName, p1); p1.validateTree(PipelineConfigSaveValidationContext.forChain(true, groupName, cruiseConfig, p1)); Assert.assertFalse(p1.errors().isEmpty()); assertThat(p1.errors().on(PipelineConfigs.GROUP), contains("Invalid group name '%$-with-invalid-characters'")); } private StageConfig getStageConfig(String stageName, String jobName) { JobConfig jobConfig = new JobConfig(new CaseInsensitiveString(jobName)); jobConfig.addTask(new AntTask()); jobConfig.addTask(new ExecTask("command", "", "workingDir")); jobConfig.artifactPlans().add(new ArtifactPlan("src", "dest")); jobConfig.addVariable("env1", "val1"); jobConfig.addResource("powerful"); JobConfigs jobConfigs = new JobConfigs(jobConfig); return new StageConfig(new CaseInsensitiveString(stageName), jobConfigs); } }