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  }