模拟Jenkins流水线步骤

7

我有一个类,在我的Jenkinsfile中使用,这里是它的简化版本:

class TestBuild {
    def build(jenkins) {
        jenkins.script {
            jenkins.sh(returnStdout: true, script: "echo build")
        }
    }
}

当在jenkinsfile中使用时,我将this作为参数提供。在这里模拟具有脚本和sh的jenkins对象的最佳方法是什么? 感谢您的帮助。

1个回答

11

我上周遇到了类似的问题,我想出了这个解决方案:

import org.jenkinsci.plugins.workflow.cps.CpsScript

def mockCpsScript() {
    return [
        'sh': { arg ->
            def script
            def returnStdout
            // depending on sh is called arg is either a map or a string vector with arguments
            if (arg.length == 1 && arg[0] instanceof Map) {
                script = arg[0]['script']
                returnStdout = arg[0]['returnStdout']
            } else {
                script = arg[0]
            }
            println "Calling sh with script: ${script}"
        },
        'script' : { arg ->
              arg[0]()
        },
    ] as CpsScript
}

并且与您的脚本一起使用(扩展了非命名 sh 调用):

class TestBuild {
    def build(jenkins) {
        jenkins.script {
            jenkins.sh(returnStdout: true, script: "echo build")
            jenkins.sh("echo no named arguments")
        }
    }
}

def obj = new TestBuild()
obj.build(mockCpsScript())

它的输出结果为:

[Pipeline] echo
Calling sh with script: echo build
[Pipeline] echo
Calling sh with script: echo no named arguments

现在这本身并不是很有用,但是很容易添加定义模拟方法行为的逻辑。例如,这个版本控制由readFile返回的内容取决于正在读取的目录和文件:

import org.jenkinsci.plugins.workflow.cps.CpsScript

def mockCpsScript(Map<String, String> readFileMap) {
    def currentDir = null
    return [
        'dir' : { arg ->
            def dir = arg[0]
            def subClosure = arg[1]
            if (currentDir != null) {
                throw new IllegalStateException("Dir '${currentDir}' is already open, trying to open '${dir}'")
            }
            currentDir = dir
            try {
                subClosure()
            } finally {
                currentDir = null
            }
        },
        'echo': { arg ->
            println(arg[0])
        },
        'readFile' : { arg ->
            def file = arg[0]
            if (currentDir != null) {
                file = currentDir + '/' + file
            }
            def contents = readFileMap[file]
            if (contents == null) {
                throw new IllegalStateException("There is no mapped file '${file}'!")
            }
            return contents
        },
        'script' : { arg ->
              arg[0]()
        },
    ] as CpsScript
}

class TestBuild {
    def build(jenkins) {
        jenkins.script {
            jenkins.dir ('a') {
                jenkins.echo(jenkins.readFile('some.file'))
            }
            jenkins.echo(jenkins.readFile('another.file'))
        }
    }
}

def obj = new TestBuild()
obj.build(mockCpsScript(['a/some.file' : 'Contents of first file', 'another.file' : 'Some other contents']))

这将输出:

[Pipeline] echo
Contents of first file
[Pipeline] echo
Some other contents
如果您需要使用currentBuild或类似的属性,那么您需要在闭包强制执行之后分配这些属性:
import org.jenkinsci.plugins.workflow.cps.CpsScript

def mockCpsScript() {
    def jenkins = [
        // same as above
    ] as CpsScript
    jenkins.currentBuild = [
        // Add attributes you need here. E.g. result:
        result:null,
    ]
    return jenkins
}

非常感谢,这是一个很棒的答案! - midori
你知道哪个库涵盖了Stage或Node的级别吗? - midori
这个 CpsScript 类涵盖了脚本和其中可以使用的任何内容,但是 Jenkins 流水线由 node->stage->script->etc 组成,所以我在想您可能知道哪个类涵盖了 stage 或 node 块? - midori
啊哈,我觉得我明白了你的问题。如果你想模拟除了我展示过的步骤/块以外的行为,那么你只需要将它们作为mockCpsScript返回的映射条目添加即可。例如,你可以通过添加 'node': {arg -> ...} 条目到返回的映射中轻松地为节点步骤添加条目。 - Jon S
@tinySandy,今天我也遇到了currentBuild的同样问题,并找到了解决方案。请看我的更新示例末尾。 - Jon S
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接