github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/test/groovy/CommonStepsTest.groovy (about) 1 import static java.util.stream.Collectors.toList 2 import static org.hamcrest.Matchers.empty 3 import static org.hamcrest.Matchers.equalTo 4 import static org.hamcrest.Matchers.is 5 import static org.junit.Assert.assertThat 6 import static org.junit.Assert.fail 7 import static util.StepHelper.getSteps 8 9 import java.io.File 10 import java.util.stream.Collectors 11 import java.lang.reflect.Field 12 13 import org.codehaus.groovy.runtime.metaclass.MethodSelectionException 14 import org.hamcrest.Matchers 15 import org.junit.Assert 16 import org.junit.Rule 17 import org.junit.Test 18 import org.junit.rules.ExpectedException 19 import org.junit.rules.RuleChain 20 21 import groovy.io.FileType 22 import hudson.AbortException 23 import util.BasePiperTest 24 import util.JenkinsReadYamlRule 25 import util.JenkinsStepRule 26 import util.Rules 27 28 /* 29 * Intended for collecting generic checks applied to all steps. 30 */ 31 public class CommonStepsTest extends BasePiperTest{ 32 33 @Rule 34 public RuleChain ruleChain = Rules.getCommonRules(this) 35 .around(new JenkinsReadYamlRule(this)) 36 37 /* 38 * With that test we ensure the very first action inside a method body of a call method 39 * for a not white listed step is the check for the script handed over properly. 40 * Actually we assert for the exception type (AbortException) and for the exception message. 41 * In case a new step is added this step will fail. It is the duty of the author of the 42 * step to either follow the pattern of checking the script first or to add the step 43 * to the white list. 44 */ 45 @Test 46 public void scriptReferenceNotHandedOverTest() { 47 // all steps not adopting the usual pattern of working with the script. 48 def whitelistScriptReference = [ 49 'abapEnvironmentPipeline', 50 'buildSetResult', 51 'commonPipelineEnvironment', 52 'handlePipelineStepErrors', 53 'pipelineExecute', 54 'piperExecuteBin', 55 'piperPipeline', 56 'prepareDefaultValues', 57 'runClosures', 58 'setupCommonPipelineEnvironment', 59 ] 60 61 List steps = getSteps().stream() 62 .filter {! whitelistScriptReference.contains(it)} 63 .forEach {checkReference(it)} 64 } 65 66 private void checkReference(step) { 67 68 try { 69 def script = loadScript("${step}.groovy") 70 71 try { 72 73 System.setProperty('com.sap.piper.featureFlag.failOnMissingScript', 'true') 74 75 try { 76 script.call([:]) 77 } catch(AbortException | MissingMethodException e) { 78 throw e 79 } catch(Exception e) { 80 fail "Unexpected exception ${e.getClass().getName()} caught from step '${step}': ${e.getMessage()}" 81 } 82 fail("Expected AbortException not raised by step '${step}'") 83 84 } catch(MissingMethodException e) { 85 86 // can be improved: exception handling as some kind of control flow. 87 // we can also check for the methods and call the appropriate one. 88 89 try { 90 script.call([:]) {} 91 } catch(AbortException e1) { 92 throw e1 93 } catch(Exception e1) { 94 fail "Unexpected exception ${e1.getClass().getName()} caught from step '${step}': ${e1.getMessage()}" 95 } 96 fail("Expected AbortException not raised by step '${step}'") 97 } 98 99 } catch(AbortException e) { 100 assertThat("Step ''${step} does not fail with expected error message in case mandatory parameter 'script' is not provided.", 101 e.getMessage() ==~ /.*\[ERROR\]\[.*\] No reference to surrounding script provided with key 'script', e.g. 'script: this'./, 102 is(equalTo(true))) 103 } finally { 104 System.clearProperty('com.sap.piper.featureFlag.failOnMissingScript') 105 } 106 } 107 108 private static fieldRelatedWhitelist = [ 109 'abapAddonAssemblyKitCheckCVs', //implementing new golang pattern without fields 110 'abapAddonAssemblyKitCheckPV', //implementing new golang pattern without fields 111 'abapAddonAssemblyKitCreateTargetVector', //implementing new golang pattern without fields 112 'abapAddonAssemblyKitPublishTargetVector', //implementing new golang pattern without fields 113 'abapAddonAssemblyKitRegisterPackages', //implementing new golang pattern without fields 114 'abapAddonAssemblyKitReleasePackages', //implementing new golang pattern without fields 115 'abapAddonAssemblyKitReserveNextPackages', //implementing new golang pattern without fields 116 'abapEnvironmentBuild', //implementing new golang pattern without fields 117 'abapEnvironmentAssemblePackages', //implementing new golang pattern without fields 118 'abapEnvironmentAssembleConfirm', //implementing new golang pattern without fields 119 'abapEnvironmentCheckoutBranch', //implementing new golang pattern without fields 120 'abapEnvironmentCloneGitRepo', //implementing new golang pattern without fields 121 'abapEnvironmentCreateTag', //implementing new golang pattern without fields 122 'abapEnvironmentPullGitRepo', //implementing new golang pattern without fields 123 'abapEnvironmentPipeline', // special step (infrastructure) 124 'abapEnvironmentRunATCCheck', //implementing new golang pattern without fields 125 'abapEnvironmentRunAUnitTest', //implementing new golang pattern without fields 126 'abapEnvironmentCreateSystem', //implementing new golang pattern without fields 127 'abapEnvironmentPushATCSystemConfig', //implementing new golang pattern without fields 128 'artifactPrepareVersion', 129 'cloudFoundryCreateService', //implementing new golang pattern without fields 130 'cloudFoundryCreateServiceKey', //implementing new golang pattern without fields 131 'cloudFoundryCreateSpace', //implementing new golang pattern without fields 132 'cloudFoundryDeleteService', //implementing new golang pattern without fields 133 'cloudFoundryDeleteSpace', //implementing new golang pattern without fields 134 'cloudFoundryDeploy', //implementing new golang pattern without fields 135 'cnbBuild', //implementing new golang pattern without fields 136 'durationMeasure', // only expects parameters via signature 137 'prepareDefaultValues', // special step (infrastructure) 138 'piperPipeline', // special step (infrastructure) 139 'pipelineStashFilesAfterBuild', // intended to be called from pipelineStashFiles 140 'pipelineStashFilesBeforeBuild', // intended to be called from pipelineStashFiles 141 'pipelineStashFiles', // only forwards to before/after step 142 'pipelineExecute', // special step (infrastructure) 143 'commonPipelineEnvironment', // special step (infrastructure) 144 'handlePipelineStepErrors', // special step (infrastructure) 145 'piperStageWrapper', //intended to be called from within stages 146 'buildSetResult', 147 'runClosures', 148 'checkmarxExecuteScan', //implementing new golang pattern without fields 149 'checkmarxOneExecuteScan', //implementing new golang pattern without fields 150 'githubCreateIssue', //implementing new golang pattern without fields 151 'githubCreatePullRequest', //implementing new golang pattern without fields 152 'githubPublishRelease', //implementing new golang pattern without fields 153 'githubCheckBranchProtection', //implementing new golang pattern without fields 154 'githubCommentIssue', //implementing new golang pattern without fields 155 'githubSetCommitStatus', //implementing new golang pattern without fields 156 'kubernetesDeploy', //implementing new golang pattern without fields 157 'piperExecuteBin', //implementing new golang pattern without fields 158 'protecodeExecuteScan', //implementing new golang pattern without fields 159 'xsDeploy', //implementing new golang pattern without fields 160 'npmExecuteScripts', //implementing new golang pattern without fields 161 'npmExecuteLint', //implementing new golang pattern without fields 162 'malwareExecuteScan', //implementing new golang pattern without fields 163 'mavenBuild', //implementing new golang pattern without fields 164 'mavenExecute', //implementing new golang pattern without fields 165 'mavenExecuteIntegration', //implementing new golang pattern without fields 166 'mavenExecuteStaticCodeChecks', //implementing new golang pattern without fields 167 'mtaBuild', //implementing new golang pattern without fields 168 'nexusUpload', //implementing new golang pattern without fields 169 'piperPipelineStageArtifactDeployment', //stage without step flags 170 'pipelineCreateScanSummary', //stage without step flags 171 'sonarExecuteScan', //implementing new golang pattern without fields 172 'gctsCreateRepository', //implementing new golang pattern without fields 173 'gctsRollback', //implementing new golang pattern without fields 174 'gctsExecuteABAPQualityChecks', //implementing new golang pattern without fields 175 'gctsExecuteABAPUnitTests', //implementing new golang pattern without fields 176 'gctsCloneRepository', //implementing new golang pattern without fields 177 'codeqlExecuteScan', //implementing new golang pattern without fields 178 'credentialdiggerScan', //implementing new golang pattern without fields 179 'fortifyExecuteScan', //implementing new golang pattern without fields 180 'gctsDeploy', //implementing new golang pattern without fields 181 'containerSaveImage', //implementing new golang pattern without fields 182 'detectExecuteScan', //implementing new golang pattern without fields 183 'kanikoExecute', //implementing new golang pattern without fields 184 'karmaExecuteTests', //implementing new golang pattern without fields 185 'gitopsUpdateDeployment', //implementing new golang pattern without fields 186 'vaultRotateSecretId', //implementing new golang pattern without fields 187 'deployIntegrationArtifact', //implementing new golang pattern without fields 188 'newmanExecute', //implementing new golang pattern without fields 189 'terraformExecute', //implementing new golang pattern without fields 190 'whitesourceExecuteScan', //implementing new golang pattern without fields 191 'uiVeri5ExecuteTests', //implementing new golang pattern without fields 192 'integrationArtifactDeploy', //implementing new golang pattern without fields 193 'integrationArtifactUpdateConfiguration', //implementing new golang pattern without fields 194 'integrationArtifactGetMplStatus', //implementing new golang pattern without fields 195 'integrationArtifactGetServiceEndpoint', //implementing new golang pattern without fields 196 'integrationArtifactDownload', //implementing new golang pattern without fields 197 'integrationArtifactUpload', //implementing new golang pattern without fields 198 'integrationArtifactTransport', //implementing new golang pattern without fields 199 'integrationArtifactTriggerIntegrationTest', //implementing new golang pattern without fields 200 'integrationArtifactUnDeploy', //implementing new golang pattern without fields 201 'integrationArtifactResource', //implementing new golang pattern without fields 202 'containerExecuteStructureTests', //implementing new golang pattern without fields 203 'transportRequestUploadSOLMAN', //implementing new golang pattern without fields 204 'transportRequestReqIDFromGit', //implementing new golang pattern without fields 205 'transportRequestDocIDFromGit', //implementing new golang pattern without fields 206 'gaugeExecuteTests', //implementing new golang pattern without fields 207 'batsExecuteTests', //implementing new golang pattern without fields 208 'transportRequestUploadRFC', //implementing new golang pattern without fields 209 'writePipelineEnv', //implementing new golang pattern without fields 210 'readPipelineEnv', //implementing new golang pattern without fields 211 'transportRequestUploadCTS', //implementing new golang pattern without fields 212 'isChangeInDevelopment', //implementing new golang pattern without fields 213 'golangBuild', //implementing new golang pattern without fields 214 'helmExecute', //implementing new golang pattern without fields 215 'apiProxyDownload', //implementing new golang pattern without fields 216 'apiKeyValueMapDownload', //implementing new golang pattern without fields 217 'apiProviderDownload', //implementing new golang pattern without fields 218 'apiProxyUpload', //implementing new golang pattern without fields 219 'gradleExecuteBuild', //implementing new golang pattern without fields 220 'shellExecute', //implementing new golang pattern without fields 221 'apiKeyValueMapUpload', //implementing new golang pattern without fields 222 'apiProviderUpload', //implementing new golang pattern without fields 223 'pythonBuild', //implementing new golang pattern without fields 224 'awsS3Upload', 225 'apiProxyList', //implementing new golang pattern without fields 226 'azureBlobUpload', 227 'awsS3Upload', 228 'ansSendEvent', 229 'apiProviderList', //implementing new golang pattern without fields 230 'tmsUpload', 231 'tmsExport', 232 ] 233 234 @Test 235 public void generalConfigKeysSetPresentTest() { 236 237 def fieldName = 'GENERAL_CONFIG_KEYS' 238 // the steps added to the fieldRelatedWhitelist do not take the general config at all 239 def stepsWithoutGeneralConfigKeySet = fieldCheck(fieldName, fieldRelatedWhitelist.plus(['gaugeExecuteTests', 240 'pipelineRestartSteps'])) 241 242 assertThat("Steps without ${fieldName} field (or that field is not a Set): ${stepsWithoutGeneralConfigKeySet}", 243 stepsWithoutGeneralConfigKeySet, is(empty())) 244 } 245 246 @Test 247 public void stepConfigKeysSetPresentTest() { 248 249 def fieldName = 'STEP_CONFIG_KEYS' 250 def stepsWithoutStepConfigKeySet = fieldCheck(fieldName, fieldRelatedWhitelist.plus('setupCommonPipelineEnvironment')) 251 252 assertThat("Steps without ${fieldName} field (or that field is not a Set): ${stepsWithoutStepConfigKeySet}", 253 stepsWithoutStepConfigKeySet, is(empty())) 254 } 255 256 @Test 257 public void parametersKeysSetPresentTest() { 258 259 def fieldName = 'PARAMETER_KEYS' 260 def stepsWithoutParametersKeySet = fieldCheck(fieldName, fieldRelatedWhitelist.plus('setupCommonPipelineEnvironment')) 261 262 assertThat("Steps without ${fieldName} field (or that field is not a Set): ${stepsWithoutParametersKeySet}", 263 stepsWithoutParametersKeySet, is(empty())) 264 } 265 266 private fieldCheck(fieldName, whitelist) { 267 268 def stepsWithoutGeneralConfigKeySet = [] 269 270 for(def step in getSteps()) { 271 if(whitelist.contains(step)) continue 272 273 def fields = loadScript("${step}.groovy").getClass().getDeclaredFields() as Set 274 Field generalConfigKeyField = fields.find{ it.getName() == fieldName} 275 if(! generalConfigKeyField || 276 ! generalConfigKeyField 277 .getType() 278 .isAssignableFrom(Set.class)) { 279 stepsWithoutGeneralConfigKeySet.add(step) 280 } 281 } 282 return stepsWithoutGeneralConfigKeySet 283 } 284 285 @Test 286 public void stepsWithWrongFieldNameTest() { 287 288 def whitelist = [ 289 'abapEnvironmentPipeline', 290 'commonPipelineEnvironment', 291 'piperPipeline', 292 'piperExecuteBin', 293 'buildSetResult', 294 'runClosures' 295 ] 296 297 def stepsWithWrongStepName = [] 298 299 for(def step in getSteps()) { 300 301 if(whitelist.contains(step)) continue 302 303 def script = loadScript("${step}.groovy") 304 305 def fields = script.getClass().getDeclaredFields() as Set 306 Field stepNameField = fields.find { it.getName() == 'STEP_NAME'} 307 308 if(! stepNameField) { 309 stepsWithWrongStepName.add(step) 310 continue 311 } 312 313 boolean notAccessible = false 314 def fieldName 315 316 if(!stepNameField.isAccessible()) { 317 stepNameField.setAccessible(true) 318 notAccessible = true 319 } 320 321 try { 322 fieldName = stepNameField.get(script) 323 } finally { 324 if(notAccessible) stepNameField.setAccessible(false) 325 } 326 if(fieldName != step) { 327 stepsWithWrongStepName.add(step) 328 } 329 } 330 331 assertThat("Steps with wrong step name or without STEP_NAME field.: ${stepsWithWrongStepName}", 332 stepsWithWrongStepName, is(empty())) 333 } 334 335 /* 336 * With that test we ensure that all return types of the call methods of all the steps 337 * are void. Return types other than void are not possible when running inside declarative 338 * pipelines. Parameters shared between several steps needs to be shared via the commonPipelineEnvironment. 339 */ 340 @Test 341 public void returnTypeForCallMethodsIsVoidTest() { 342 343 def stepsWithCallMethodsOtherThanVoid = [] 344 345 def whitelist = [ 346 'durationMeasure', 347 'mavenExecute' 348 ] 349 350 for(def step in getSteps()) { 351 def methods = loadScript("${step}.groovy").getClass().getDeclaredMethods() as List 352 Collection callMethodsWithReturnTypeOtherThanVoid = 353 methods.stream() 354 .filter { ! whitelist.contains(step) } 355 .filter { it.getName() == 'call' && 356 it.getReturnType() != Void.TYPE } 357 .collect(toList()) 358 if(!callMethodsWithReturnTypeOtherThanVoid.isEmpty()) stepsWithCallMethodsOtherThanVoid << step 359 } 360 361 assertThat("Steps with call methods with return types other than void: ${stepsWithCallMethodsOtherThanVoid}", 362 stepsWithCallMethodsOtherThanVoid, is(empty())) 363 } 364 }