I am calling this function from a declarative pipeline in the post always stage:
import groovy.json.JsonSlurper
import com.cloudbees.groovy.cps.NonCPS
@NonCPS
def call(){
String path = "C:\\Users\\tests"
String jsonFileToRead = new File(path).listFiles().findAll { it.name.endsWith(".json") }
.sort { -it.lastModified() }?.head()
JsonSlurper jsonSlurper = new JsonSlurper()
Map data = jsonSlurper.parse(new File(jsonFileToRead))
Map testResults = [:]
int totalTests = data.results.size()
int totalFailures = 0
int totalSuccess = 0
def results = []
//Map test names and results from json file
for (int i = 0; i < data.results.size(); i ) {
testResults.put(i, [data['results'][i]['TestName'], data['results'][i]['result']])
}
//Iterate through the map and send to slack test name and results and then a summary of the total tests, failures and success
for (test in testResults.values()){
String testName = test[0]
String testResult = test[1]
String msg = "Test: " testName " Result: " testResult
if(testResult == "fail")
{
totalFailures
println msg
try {
slackSend color : "danger", message: "${msg}", channel: '#somechannel'
} catch (Throwable e) {
error "Caught ${e.toString()}"
}
}
else if(testResult == "pass")
{
totalSuccess
println msg
try {
slackSend color : "good", message: "${msg}", channel: '#somechannel'
} catch (Throwable e) {
error "Caught ${e.toString()}"
}
}
else
{
println "Unknown test result: " testResult
}
}
def resultsSummary = "Total Tests: " totalTests " Total Failures: " totalFailures " Total Success: " totalSuccess
slackSend color : "good", message: "${resultsSummary}", channel: '#somechannel'
println resultsSummary
but I keep getting this error, even though I am using the @NonCPS. I am bit confused as if I comment out the 3 lines where I call the slackSend functionality everything goes fine and I get in the console output the right messages and the pipeline finishes succesfully, but if I run it like it is I get this error even if I just try to send the summary and comment out the other 2 slackSend:
13:53:23 [Pipeline] echo
13:53:23 Test: triggers_attackMove Result: pass
13:53:23 [Pipeline] slackSend
13:53:23 Slack Send Pipeline step running, values are - baseUrl: <empty>, teamDomain: forgottenempires, channel: #test_harness_logs_phoenix, color: good, botUser: true, tokenCredentialId: Slack_bot_test, notifyCommitters: false, iconEmoji: :goose, username: Jenkins Wizard, timestamp: <empty>
13:53:23 [Pipeline] error
13:53:24 [Pipeline] }
13:53:24 [Pipeline] // script
13:53:24 Error when executing always post condition:
13:53:24 hudson.AbortException: Caught CpsCallableInvocation{methodName=slackSend, call=com.cloudbees.groovy.cps.impl.CpsFunction@149776fc, receiver=null, arguments=[org.jenkinsci.plugins.workflow.cps.DSL$ThreadTaskImpl@25def8]}
13:53:24 at org.jenkinsci.plugins.workflow.steps.ErrorStep$Execution.run(ErrorStep.java:64)
13:53:24 at org.jenkinsci.plugins.workflow.steps.ErrorStep$Execution.run(ErrorStep.java:51)
13:53:24 at org.jenkinsci.plugins.workflow.steps.SynchronousStepExecution.start(SynchronousStepExecution.java:37)
13:53:24 at org.jenkinsci.plugins.workflow.cps.DSL.invokeStep(DSL.java:322)
13:53:24 at org.jenkinsci.plugins.workflow.cps.DSL.invokeMethod(DSL.java:196)
enter code here
....
I tried to take the slackSend functionality to another function with the @NonCPS decorator and call it from within the function but I keep getting the same error, and honestly I am quiet lost a this point
CodePudding user response:
slackSend
is a pipeline step and as such can't be called from @NonCPS
context. There are only a few exceptions like echo
, that work from @NonCPS
context. See Use of Pipeline steps from @NonCPS for details.
My approach in such cases is to remove @NonCPS
from the function that calls the step(s) and move only the code that actually requires @NonCPS
into separate @NonCPS
functions. In your case that's propably just the code that uses the File
and JsonSlurper
classes.
Also, we need to make sure that any data returned by the @NonCPS
functions is serializable. JsonSlurper
returns a LazyMap
that isn't. As OP commented, the easiest solution is to use JsonSlurperClassic
instead, which returns a regular, serializable Map
(or a List
in case the root is an array).
@NonCPS
Map readData(){
String path = "C:\\Users\\tests"
String jsonFileToRead = new File(path).listFiles().findAll { it.name.endsWith(".json") }
.sort { -it.lastModified() }?.head()
def jsonSlurper = new JsonSlurperClassic()
return jsonSlurper.parse(new File(jsonFileToRead))
}
// Note: Must not be @NonCPS because pipeline steps are called!
def call(){
Map data = readData()
Map testResults = [:]
int totalTests = data.results.size()
int totalFailures = 0
int totalSuccess = 0
def results = []
//Map test names and results from json file
for (int i = 0; i < data.results.size(); i ) {
testResults.put(i, [data['results'][i]['TestName'], data['results'][i]['result']])
}
//Iterate through the map and send to slack test name and results and then a summary of the total tests, failures and success
for (test in testResults.values()){
String testName = test[0]
String testResult = test[1]
String msg = "Test: " testName " Result: " testResult
if(testResult == "fail")
{
totalFailures
println msg
try {
slackSend color : "danger", message: "${msg}", channel: '#somechannel'
} catch (Throwable e) {
error "Caught ${e.toString()}"
}
}
else if(testResult == "pass")
{
totalSuccess
println msg
try {
slackSend color : "good", message: "${msg}", channel: '#somechannel'
} catch (Throwable e) {
error "Caught ${e.toString()}"
}
}
else
{
println "Unknown test result: " testResult
}
}
def resultsSummary = "Total Tests: " totalTests " Total Failures: " totalFailures " Total Success: " totalSuccess
slackSend color : "good", message: "${resultsSummary}", channel: '#somechannel'
println resultsSummary
}