Test Jenkins Pipeline And SHared LIBRARIES

Stanislav Ovchar

stchar

pipeline

Jenkins

  • Simple pipline
  • in-browser editor 
  • Manual testing

pipeline

JobDSL

Configuration

Seeder

Jenkins

  • CI
  • Generate jobs with a script
  • Manual testing

pipeline

JobDSL

E2E Test

Configuration

Seeder

Jenkins

  • Almost CD
  • Test and Development require Jenkins instance

pipeline

JobDSL

Unit-Test

E2E Test

Configuration

Seeder

Jenkins

  • Jobs could be partially tested on the localhost

problemS

  • more jobs = more code = more efforts
     
  • Jenkins out of the box has a lack of framework or tools to debug and test
     
  • jobDSL configuration should be tested as well

E2E Tests

Unit tests 

Integration
Tests

Sandbox project

 

Split big pipeline in small pieces

 

load'em, test'em one by one

 

E2E Tests

Unit tests 

Integration
Tests

@JenkinsRule

@DockerRule

 

Verify jobDsl configuration and your seeder factory

 

Test Release mail

Test input step

E2E Tests

Unit tests 

Integration
Tests

Mock (sh, git, parallel, shared lib)

 

Check pipeline "business-logic"

Demo Pipeline Test (gradle)

stchar/pipeline-dsl-seed

#> ./gradlew test
#> git clone https://github.com/stchar/pipeline-dsl-seed.git


.
├── jobs
│   ├── config.groovy     # contains a configuration of jobs to deploy
│   │
│   ├── seeder.groovy     # Groovy script to seed jobs with provided configuration
│   │
│   ├── seed                       # A project to test and publish
│   │   │                          # your Jenkins jobs
│   │   │
│   │   ├── dsl
│   │   │   └── job-publisher.groovy  # default input arguments
│   │   │                             # configuration to call seeder script
|   │   │                             # and is used to sand-boxing the projects
│   │   └── pipeline
│   │       └── job-publisher.groovy  # Contains pipeline script to read the
|   │                                 # configuration, and to call seeder script
│   │
│   └── template                      # project with examples of scripts
│       ├── dsl
│       │   └── template.groovy
│       ├── pipeline
│       │   └── template.groovy
│       └── test
│           └── template_SUITE.groovy # e2e test
└── src
    └── test
        └── groovy
            ├── JobSriptsSpec.groovy    # Reads the configuration
            │                           # runs deployment in a test environment
            │
            ├── TestJobPublisher.groovy # Test job deployment script
            │
            └── TestTemplateJob.groovy  # Example of junit tests written using JenkinsPipelineUnit
                                        # to test template job
// file: jobs/template/pipeline/template.groovy

node() {
  echo "Hello World!"
}
// file: jobs/template/dsl/template.groovy

def pipeline(dslFactory,seed_ref,job) {

  if (seed_ref =~ /master/) {
    _quietPeriod = 10
  } else {
    _quietPeriod = 1
  }

  dslFactory.with {
    quietPeriod(_quietPeriod)
    concurrentBuild(true)
  }
}
// file: src/test/groovy/TestTemplateJob.groovy

import org.junit.Before
import org.junit.Test
import static org.junit.Assert.assertTrue

import com.lesfurets.jenkins.unit.BasePipelineTest
import static com.lesfurets.jenkins.unit.MethodCall.callArgsToString

class TestTemplateJob extends BasePipelineTest {

  @Override
  @Before
  void setUp() throws Exception {
      scriptRoots += 'jobs'
      super.setUp()
  }

// ...

}

// file: src/test/groovy/TestTemplateJob.groovy

import static com.lesfurets.jenkins.unit.MethodCall.callArgsToString

...

@Test
void should_print_property_value() {
  def script = runScript('template/pipeline/template.groovy')

  def expectedValue = 'Hello World!'
  assertTrue(helper.callStack.findAll { call ->
    call.methodName == 'echo'
  }.any { call ->
    callArgsToString(call).contains(expectedValue)
  })
}

Demo Pipeline Test (docker)

stchar/pipeline-dsl-seed

INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@3c943db4: defining beans [authenticationManager]; root of factory hierarchy
Jul 27, 2017 4:23:10 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.web.context.support.StaticWebApplicationContext@57615d2e: display name [Root WebApplicationContext]; startup date [Thu Jul 27 16:23:10 UTC 2017]; root of context hierarchy
Jul 27, 2017 4:23:10 PM org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory
INFO: Bean factory for application context [org.springframework.web.context.support.StaticWebApplicationContext@57615d2e]: org.springframework.beans.factory.support.DefaultListableBeanFactory@6e050b6c
Jul 27, 2017 4:23:10 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@6e050b6c: defining beans [filter,legacy]; root of factory hierarchy
Jul 27, 2017 4:23:10 PM jenkins.install.SetupWizard init
INFO: 

*************************************************************
*************************************************************
*************************************************************

Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:

a7290d982c39456e81c2be180e4301ef

This may also be found at: /var/jenkins_home/secrets/initialAdminPassword

*************************************************************
*************************************************************
*************************************************************

Jul 27, 2017 4:23:18 PM hudson.model.UpdateSite updateData
INFO: Obtained the latest update center data file for UpdateSource default
Jul 27, 2017 4:23:18 PM hudson.model.UpdateSite updateData
INFO: Obtained the latest update center data file for UpdateSource d

Ctrl+C

#> docker run -p 8080:8080 -p 50000:50000 jenkins

Ctrl+V

jobs/seed/pipeline/job-publisher.groovy

gradle downloads the entire internet)))

script approval

Install jobDsl plugin

TeSt Shared Libraries

pipeline-template

Unit-Tests

  • src/test
  • template to load the lib
     
  • cp -r src vars  miylib@master
  • gradle check

 

 

  • JenkinsPipelineUnit

 

 

  • @JenkinsRule

src, vars

Integration

Tests

Demo Shared Lib Test (gradle)

stchar/pipeline-sharedlib-testharness

#> gradle check
#> git clone https://github.com/stchar/pipeline-sharedlib-testharness.git


.
│
├── build.gradle
│
├── build
│   │
│   └── libs
│       └── jenkins-commons@master
│       └── jenkins-commons@feature
│   
├── jobs
│   └── template
│       └── pipeline
│           └── template.groovy
│
├── src
│   ├── org
│   │   └── hcm
│   │       └── libjenkins
│   │           └── Gitlab.groovy
│   └── test
│       └── groovy
│           └── TestJenkinsCommonLib.groovy
└── vars
    └── gitlab_stage.groovy

// file: src/test/groovy/TestJenkinsCommonLib.groovy

@Library('jenkins-commons')
import org.hcm.libjenkins.Gitlab

gitlab_lib = new Gitlab(this)

gl_stages = ['hello']

gitlabBuild(gl_stages) {
  gitlab_stage(gl_stages,'hello','master',10) {
    echo "Hello World!"
  }
}

return this
// file: src/test/groovy/TestJenkinsCommonLib.groovy

...

class TestJenkinsCommonLib extends BasePipelineTest {

  String sharedLibs = ''

  @Override
  @Before
  void setUp() throws Exception {
      scriptRoots += 'jobs'
      super.setUp()
      def library = library().name('jenkins-commons')
                       .defaultVersion("master")
                       .allowOverride(true)
                       .implicit(true)
                       .targetPath('build/libs')
                       .retriever(localSource('build/libs'))
                       .build()
      helper.registerSharedLibrary(library)
  }

 
}
// file:  src/test/groovy/TestJenkinsCommonLib.groovy

 ...

  @Test
  void should_execute_without_errors() throws Exception {
    def script = runScript("template/pipeline/template.groovy")
    //printCallStack()
  }

  @Test
  void verify_is_upstream() throws Exception {
    def script = runScript("template/pipeline/template.groovy")
    assertEquals('Verify is_upstream ', false, script.gitlab_lib.is_upstream('feature'))
    assertEquals('Verify is_upstream ', true, script.gitlab_lib.is_upstream('master'))
    assertEquals('Verify is_upstream ', true, script.gitlab_lib.is_upstream('rel-1.2.3'))
    //printCallStack()
  }

LINKS

  • https://github.com/stchar/pipeline-dsl-seed
  • https://github.com/stchar/pipeline-sharedlib-testharness
  • https://github.com/lesfurets/JenkinsPipelineUnit
  • https://github.com/sheehan/job-dsl-gradle-example
  • https://github.com/mkobit/jenkins-pipeline-shared-libraries-gradle-plugin

JPipelineUnit en_US

By Stas Ovchar

JPipelineUnit en_US

CD for CD and X as code in Jenkins pipeline world. Testing of Jenkins pipelines and shared libraries with different frameworks.

  • 953