【问题标题】:Why does stash/unstash not work in this Jenkinsfile?为什么 stash/unstash 在这个 Jenkinsfile 中不起作用?
【发布时间】:2017-11-29 12:57:21
【问题描述】:

我有一个在现场运行的 Jenkins 服务器,它使用 Jenkinsfile 来管理一个管道,该管道使用并行测试执行器插件在多个代理上运行我的所有 JUnit 测试以加快测试速度。我们有一台自己制造的刀片服务器(比购买一台便宜得多!),它把我们的测试时间从近 2 小时缩短到了 22 分钟。 JUnit 插件非常适合并行测试。

Jacoco 插件却没有。所以我试图将覆盖文件合并到一个文件中,以便 Jacoco 插件可以发布覆盖结果。 Stash/unstash 在源存储中起作用,但是当我尝试存储不同的 Jacoco 输出文件以在主服务器上取消存储它们时,它不起作用。

有什么想法吗?

这是我的 Jenkins 文件:

#!/usr/bin/env groovy

def branch
def hash

node('remote') {
  sh 'echo starting'

  branch = env.gitlabBranch ?: '**'
  echo "Branch: $branch"

  checkout([$class: 'GitSCM',
        branches: [[name: "$branch"]],
        extensions: [
          [$class: 'PruneStaleBranch'],
          [$class: 'CheckoutOption', timeout: 120],
          [$class: 'CloneOption', depth: 0, noTags: true, shallow: true, timeout: 180]
        ],
        doGenerateSubmoduleConfigurations: false,
        submoduleCfg: [],
        userRemoteConfigs: [[credentialsId: 'gitlabLabptop', url: 'git@gitlab.com:protocase/my_project_url.git']]
       ]
      )

  hash = sh (script: 'git rev-parse HEAD', returnStdout: true).trim()

  ### - this stash works fine -###
  stash name: 'sources', includes: '**', excludes: '**/.git,**/.git/**'
}

def numBranches = 9
def splits = splitTests count(numBranches)
def branches = [:]

for (int i = 0; i < splits.size(); i++) {
  def index = i // fresh variable per iteration; i will be mutated

  branches["split${i}"] = {
    timeout(time: 125, unit: 'MINUTES') {
      node('remote') {
    sh 'echo starting a node'
    deleteDir()

    ### - this unstash works fine - ###
    unstash 'sources'

    def exclusions = splits.get(index);
    writeFile file: 'test/exclusions.txt', text: exclusions.join("\n")

    sh 'ant clean'

    sh 'rm -rf build'

    sh 'ant jar'

    sh 'ant -buildfile build-test.xml buildTests'

    sh 'ant -buildfile build-test.xml jenkinsBatch'

    junit 'build/test/results/*.xml'

    sh "mv build/test/jacoco/jacoco.exec build/test/jacoco/jacoco${index}.exec"
    echo "name: coverage$index, unclude jacoco${index}"

       ### - this stash appears to work - ### 
       stash name: "coverage$index", includes: "build/test/jacoco/jacoco${index}.exec"
       echo "stashed"

      }
    }
  }
}

parallel branches


def branchIndecis = 0..numBranches

node('master') {
  if (currentBuild.result != "ABORTED") {

    echo "collecting exec files"

    branchIndecis.each {
      echo "unstash coverage${it}"

      ### !!! this unstash causes an error !!! ###
      unstash name: "coverage${it}"



      echo "make file name"
      def coverageFileName = "build/test/jacoco/jacoco${it}.exec"
      echo "merge file"
      sh "ant -buildfile build-test.xml -Dfile=${coverageFileName} coverageMerge"
    }

    echo "collected exec files"

    step([$class: 'JacocoPublisher',
      execPattern:'build/test/jacoco/jacoco.exec',
      classPattern: 'build/classes',
      sourcePattern: 'src'])

    echo "finishing ${branch} - ${hash}"

  }
}

我得到的输出是:

[split7] [jdesigner] Running shell script
[split7] + mv build/test/jacoco/jacoco.exec build/test/jacoco/jacoco7.exec
[Pipeline] [split7] echo
[split7] name: coverage7, unclude jacoco7
[Pipeline] [split7] stash
[split7] Stashed 1 file(s)
[Pipeline] [split7] echo
[split7] stashed
[Pipeline] [split7] }
[Pipeline] [split7] // node
[Pipeline] [split7] }
[Pipeline] [split7] // timeout
[Pipeline] [split7] }
[Pipeline] // parallel
[Pipeline] node
Running on eightyeight in /var/jenkins/workspace/jdesigner
[Pipeline] {
[Pipeline] echo
collecting exec files
[Pipeline] echo
unstash coverage0
[Pipeline] unstash
[Pipeline] }
[Pipeline] End of Pipeline
Finished: FAILURE

[编辑] coverage0 的存储是

[split0] Recording test results
[Pipeline] [split0] sh
[split0] [jdesigner] Running shell script
[split0] + mv build/test/jacoco/jacoco.exec build/test/jacoco/jacoco0.exec
[Pipeline] [split0] echo
[split0] name: coverage0, include jacoco0
[Pipeline] [split0] stash
[split0] Stashed 1 file(s)
[Pipeline] [split0] echo
[split0] stashed
[Pipeline] [split0] }
[Pipeline] [split0] // node
[Pipeline] [split0] }
[Pipeline] [split0] // timeout
[Pipeline] [split0] }
[split3]     [junit] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 18.737 sec
[split3]     [junit] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 18.737 sec

注意这一行

[split0] name: coverage0, include jacoco0

只是我的回显语句,我从脚本的这一部分回显名称:

    sh "mv build/test/jacoco/jacoco.exec build/test/jacoco/jacoco${index}.exec"
    echo "name: coverage$index, include jacoco${index}"

    stash name: "coverage$index", includes: "build/test/jacoco/jacoco${index}.exec"
    echo "stashed"

注意实际的存储不是在节点上完成的,它被列为管道,即使它是在远程节点上完成的。我已经看到一些事情表明存储是在主服务器上完成的,但实际上并不是该目录所在的位置。

[[进一步编辑]] - 感谢 eis 的建议。

master 上的jobs/jdesigner/builds/1639/stashes/ 目录有coverage#.tar.gz 文件,其中包括相应的jacoco#.exec 文件。当我尝试取消隐藏时:

try {
    unstash name: "coverage${it}"
} catch (error) {
    echo "error unstashing: ${error}"
}

我得到的输出是:

collecting exec files
[Pipeline] echo
unstash coverage0
[Pipeline] unstash
[Pipeline] echo
error unstashing: java.io.NotSerializableException: groovy.lang.IntRange
[Pipeline] echo
make file name

【问题讨论】:

  • 输出摘录显示coverage7 的存储和coverage0 的取消存储。更有趣的是日志存储部分coverage0
  • 我已经添加了那个位@GeroldBroser

标签: java git jenkins groovy jacoco


【解决方案1】:

TLDR:这是一个迭代样式导致问题的案例,因为使用的键 it 不是 Serializable

使调试变得困难的是错误消息没有正确报告,可能是由于this issue。在代码中捕获异常并“手动”报告修复了该问题。

实际问题已通过使用 Serializable 键修复。


加长版:

因为在您的示例中这是有效的:

node('remote') {
    ### - this stash works fine -###
    stash name: 'sources', includes: '**', excludes: '**/.git,**/.git/**'
}
node('remote') {    
    ### - this unstash works fine - ###
    unstash 'sources'
}

但这不是:

node('remote') {

   ### - this stash appears to work - ### 
   stash name: "coverage$index", includes: "build/test/jacoco/jacoco${index}.exec"
   echo "stashed"

}
node('master') {
   echo "unstash coverage${it}"

   ### !!! this unstash causes an error !!! ###
   unstash name: "coverage${it}"
}

我最初认为工作的一个被隐藏在您的远程节点上,而非工作的一个被隐藏在您的远程节点上,但您尝试在您的主节点上取消它(它自然不会被发现) .

然而,事实并非如此。根据this

当您将文件存储在从属设备上时,这些文件将发送给主设备。 这些文件将存储在 Job 文件夹中,在相关的构建中 stash 文件夹下的文件夹。每个存储都将存储为 tar 文件。这些文件在构建结束时被删除。

所以主远程分离不应该有所作为。另外,如果是关于 stash 没有找到,你可以see from the sources 说它会失败,"No such saved stash ‘" + name + "’,因为根据AbortException javadoc“当这个异常被捕获时,将报告指定的消息。”。这显然不会发生。

相反,应该使用 try-catch 块进行调试,以找出破坏构建的真正异常。

至于为什么默认没有正确报告,有this issue:“构建日志中没有正确报告流程末尾的序列化错误,只有Jenkins日志”。该错误报告声称其“已修复”,但显然只是因为在新版本中,一些对此行为的测试并未触发问题,因此它可能仍然存在。

捕获到错误消息后,可以看出问题出在this - 我们在传递一个不可序列化的键时尝试对其进行序列化。

【讨论】:

  • 它被存储在一个远程节点上,并在其他 8 个远程节点上取消存储。这就是我认为 stash 和 unstash 的用途。
  • 来自 Jenkins 文档:保存一组文件以供稍后在同一构建中使用,通常在另一个节点/工作区上。 jenkins.io/doc/pipeline/steps/workflow-basic-steps/…
  • @vextorspace ...而且,如果没有这样的存储,它应该以this message 失败,因为AbortException javadoc 说“当这个异常被捕获时,将报告指定的消息。”所以我想它必须是别的东西。但是,master-remote stash sync 报告了一些问题:stackoverflow.com/questions/42508119/…
  • 我对管道插件不太熟悉,但是在尝试取消存储之前,您能否列出主节点上的存储,看看存储是否存在?
  • 另外,您可以尝试捕获异常(如 this)并打印它以获取构建失败原因的详细信息,因为我认为它不是不言而喻的。
【解决方案2】:

在 Jenkins 中使用并行进程时有两种可能性:

  1. 您可能会尝试在 stash name: "coverage$index", includes: "build/test/jacoco/jacoco${index}.exec"master 节点上完成之前在一个(或多个)进程中取消存储

  2. 进程之间可能存在名称争用。

解释(2):

进程 1 创建一个名为 stashed_files 的存储区

进程 2 存储在同名 stashed_files 下,然后成功取消存储。 stashed_files 已删除。

进程 1 尝试取消隐藏 stashed_files。由于stashed_files 已被进程 2 删除,因此在 unstashing 期间出现错误。

可以在in this question 找到一些有用的 Groovy 代码来解决这个问题。

【讨论】:

  • 未存储操作未删除存储
猜你喜欢
  • 2019-04-13
  • 2020-04-04
  • 1970-01-01
  • 1970-01-01
  • 2011-03-27
  • 2017-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多