While this is specific to Jenkins code this question I think is more generally about the metaprogramming features of Groovy.
This is what my tests looks like:
// MyDeclarativePipelineTest.groovy
class MyDeclarativePipelineTest extends DeclarativePipelineTest {
#Override
#Before
public void setUp() throws Exception {
super.setUp();
helper.registerAllowedMethod("parameters", [ArrayList.class], null)
}
}
// someTests.groovy
class someTests extends MyDeclarativePipelineTest {
#Override
#Before
public void setUp() throws Exception {
super.setUp();
}
#Test
public void pipeline_should_execute_without_errors() throws Exception {
addParam('MYPARAM', 'sdfsdfsdf')
def script = loadScript("Jenkinsfile")
script.run()
assertJobStatusSuccess()
}
}
// Jenkinsfile
pipeline {
stages {
stage('Build') {
steps {
If(true) {print("sdfsdfsd")} // should fail because not wrapped in script{} step
echo 'Building..'
}
}
}
}
I'm using this library in my Spock tests: JenkinsPipelineUnit
I want to overrided the steps{} closure in my MyDeclarativePipelineTest class
The steps closure is defined in this class StageDeclaration.groovy
StageDeclaration is used in the GenericPipelineDeclaration class like this:
def stage(String name,
#DelegatesTo(strategy = DELEGATE_FIRST, value = StageDeclaration) Closure closure) {
this.stages.put(name, createComponent(StageDeclaration, closure).with { it.name = name; it })
}
And then finally the GenericPipelineDeclaration is used in DeclarativePipelineTest like this:
def pipelineInterceptor = { Closure closure ->
GenericPipelineDeclaration.binding = binding
GenericPipelineDeclaration.createComponent(DeclarativePipeline, closure).execute(delegate)
}
I'm extending DeclarativePipelineTest with my own class MyDeclarativePipelineTest
Is there a way I can override the steps closure inside of MyDeclarativePipelineTest? I want it to throw an error if raw Groovy code is defined in the steps{} closure that isn't wrapped in a script{} closure.
Related
Below is my pipeline code :
dir(my_directory) {
retry(1) {
// something
}
}
Is there a possibility to access dir step in groovy through the pipeline context ?
I'm thinking of something like this below
class StepExecutor {
// some code
void dir(String directory, Closure statement) {
this.steps.dir(directory) { statement }
}
}
Yes you can. It requires you to pass the steps-object of the pipeline though.
class StepExecutor {
def steps;
public StepExecutor(def steps) {
this.steps = steps
}
// some code
void dir(String directory, Closure statement) {
this.steps.dir(directory) { statement }
}
}
creating the object from inside of your pipeline:
pipeline { ....
def stepExecutor = new StepExecutor(this);
...}
How can I define a class in a Jenkins shared library that contains an empty JSON object?
// src/org/build/Report.groovy
package org.build
public class Report implements Serializable {
def steps
def json
Report(steps) {
this.steps = steps
this.json = emptyJson()
}
#NonCPS
def emptyJson() {
return this.steps.readJSON( text: '{}' )
}
}
...is instantiated from this pipeline:
#Library('my-library')
import org.build.Report
pipeline {
agent any
stages {
stage("foo") {
steps {
script {
rep = new org.build.Report(this)
}
}
}
}
}
...and fails with the error: expected to call org.build.Report.<init> but wound up catching readJSON; see: https://jenkins.io/redirect/pipeline-cps-method-mismatches/
I had only earlier today thought I'd figured out how to solve this "class" of problem.
Earlier, I encountered the same error when invoking a shared-library function from a shared-library class. I fixed that problem per the guidance at the link that the error message noted, i.e. annotating the shared-library function with #NonCPS.
I.e. in the code below, class FirstClass is able to invoke function firstNonNull() because the function is annotated with #NonCPS; without the annotation, this code generated the same error as in the question above:
// src/org/example/FirstClass.groovy
package org.example
public class FirstClass implements Serializable {
def steps
def var
FirstClass(steps) {
this.steps = steps
this.var = steps.utils.firstNonNull( [null, null, "assigned_from_ctor"] )
}
}
// vars/utils.groovy
#NonCPS
def firstNonNull( arr ) {
for ( def i in arr ) { if ( i ) { return i } }
return null
}
#Library('my-library')
import org.example.FirstClass
pipeline {
agent any
stages {
stage("foo") {
steps {
script {
first_class = new org.example.FirstClass(this)
}
}
}
}
}
Why does this approach not work with the Report class invoking readJSON?
In my app I use spock in version 2.0-M1-groovy-2.5. I have a problem that even though I mokced a class I get npe from the internals of mocked method.
I have simple class called ReactiveRestHighLevelClient which looks like this
open class ReactiveRestHighLevelClient(val restHighLevelClient: RestHighLevelClient, private val objectMapper: ObjectMapper) {
...
fun index(indexRequest: IndexRequest): Mono<IndexResponse> {
return Mono.create<IndexResponse> { sink ->
restHighLevelClient.indexAsync( // < -- HERE I GET THE NPE EVEN THOUGH THE METHOD IS MOCKED
indexRequest,
object : ActionListener<IndexResponse> {
override fun onFailure(e: Exception) {
e.printStackTrace()
sink.error(e)
}
override fun onResponse(response: IndexResponse) {
sink.success(response)
}
}
)
}
}
...
}
I have class ThreadModelElasticsearchService which uses the ReactiveRestHighLevelClient and looks like this:
#Component
class ThreadModelElasticsearchService(
private val objectMapper: ObjectMapper,
private val reactiveElasticsearchClient: ReactiveRestHighLevelClient,
private val extraFactsService: ExtraFactsService,
private val customerDataService: CustomerDataService,
rateLimiterRegistry: RateLimiterRegistry
) {
...
fun save(operationId: String, threadModel: ThreadModel): Mono<ThreadModel> {
val id = threadModel.threadId
?: ThreadModel.createKey(threadModel.partnerId, threadModel.customerId)
return reactiveElasticsearchClient
.index(
IndexRequest(ThreadModel.INDEX, ThreadModel.TYPE, id)
.source(objectMapper.writeValueAsString(threadModel), XContentType.JSON)
)
.thenReturn(threadModel)
.doOnNext { logger.info("[operation_id=$operationId] -- Saved : $it") }
.onErrorMap { e ->
logger.error("[operation_id=$operationId] -- Error calling elasticSearch", e)
ElasticRepositoryError(e)
}
.rateLimit(elasticRateLimiter)
}
...
}
Finally I have my test class called ThreadModelElasticsearchServiceTest which looks like this:
class ThreadModelElasticsearchServiceTest extends Specification {
ReactiveRestHighLevelClient reactiveElasticsearchClient = Mock()
... other mocks
ThreadModelElasticsearchService threadModelReactiveElasticsearchClient = new
ThreadModelElasticsearchService(objectMapper, reactiveElasticsearchClient, extraFactsService, customerDataService, RateLimiterRegistry.of(RateLimiterConfig.ofDefaults()))
def "should work"() {
given:
ThreadModel threadModel = new ThreadModel(...)
reactiveElasticsearchClient.index(_ as IndexRequest) >> Mono.just(indexResponse)
expect:
threadModelReactiveElasticsearchClient.save("should-work", threadModel).block()
}
When I run the tests I get execption like
java.lang.NullPointerException: null
at com.cb.elastic.search.api.elasticsearch.ReactiveRestHighLevelClient$index$1.accept(ReactiveRestHighLevelClient.kt:76)
which points to the body of the index method of the ReactiveRestHighLevelClient mock which is strange.
How to solve this ?
In Swift 4, I used to do something like this to locally store objects of variable classes:
class Repo {
var mediaType : MyBaseClass.Type
func doSomething() {
mediaType.someStaticMethod();
}
}
class SpecificClass : MyBaseClass {
static func someStaticMethod() -> void {
// Stuff
}
}
repo = Repo(SpecificClass.self)
repo.doSomething(); // Executes `Stuff`
Moving to Dart 2, this is the closest I've gotten, yet the error specified at the bottom is blocking me.
class Repo {
Type mediaType;
void doSomething() {
mediaType.someStaticMethod();
}
}
class SpecificClass extends MyBaseClass {
static void someStaticMethod() {
// Whatever
}
}
repo = Repo(SpecificClass)
repo.doSomething() // Should execute `Whatever`, but for the error
Which generates this error:
The method 'someStaticMethod' isn't defined for the class 'Type'
Is this sort of trick feasible with Dart 2?
I have a JavaFX application which instantiates several Task objects.
Currently, my implementation (see below) calls the behavior runFactory() which performs computation under a Task object. Parallel to this, nextFunction() is invoked. Is there a way to have nextFunction() "wait" until the prior Task is complete?
I understand thread.join() waits until the running thread is complete, but with GUIs, there are additional layers of complexity due to the event dispatch thread.
As a matter of fact, adding thread.join() to the end of the code-segment below only ceases UI interaction.
If there are any suggestions how to make nextFunction wait until its prior function, runFactory is complete, I'd be very appreciative.
Thanks,
// High-level class to run the Knuth-Morris-Pratt algorithm.
public class AlignmentFactory {
public void perform() {
KnuthMorrisPrattFactory factory = new KnuthMorrisPrattFactory();
factory.runFactory(); // nextFunction invoked w/out runFactory finishing.
// Code to run once runFactory() is complete.
nextFunction() // also invokes a Task.
...
}
}
// Implementation of Knuth-Morris-Pratt given a list of words and a sub-string.
public class KnuthMorrisPratt {
public void runFactory() throws InterruptedException {
Thread thread = null;
Task<Void> task = new Task<Void>() {
#Override public Void call() throws InterruptedException {
for (InputSequence seq: getSequences) {
KnuthMorrisPratt kmp = new KnuthMorrisPratt(seq, substring);
kmp.align();
}
return null;
}
};
thread = new Thread(task);
thread.setDaemon(true);
thread.start();
}
When using Tasks you need to use setOnSucceeded and possibly setOnFailed to create a logic flow in your program, I propose that you also make runFactory() return the task rather than running it:
// Implementation of Knuth-Morris-Pratt given a list of words and a sub-string.
public class KnuthMorrisPratt {
public Task<Void> runFactory() throws InterruptedException {
return new Task<Void>() {
#Override public Void call() throws InterruptedException {
for (InputSequence seq: getSequences) {
KnuthMorrisPratt kmp = new KnuthMorrisPratt(seq, substring);
kmp.align();
}
return null;
}
};
}
// High-level class to run the Knuth-Morris-Pratt algorithm.
public class AlignmentFactory {
public void perform() {
KnuthMorrisPrattFactory factory = new KnuthMorrisPrattFactory();
Task<Void> runFactoryTask = factory.runFactory();
runFactoryTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent t)
{
// Code to run once runFactory() is completed **successfully**
nextFunction() // also invokes a Task.
}
});
runFactoryTask.setOnFailed(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent t)
{
// Code to run once runFactory() **fails**
}
});
new Thread(runFactoryTask).start();
}
}