Сразу стоит отметить, что я не нашел в документации к Jenkins утверждения, что Jenkinsfile пишется на Groovy. В документации к Jenkins сказано: Scripted Pipeline is a domain-specific language [3] based on Groovy, most Groovy syntax can be used in Scripted Pipeline without modification. Но количество отсылок к Groovy столь велико, что у многих людей создаются ложные ожидания. Я решил написать этот пост после многократного объяснения коллегам отличий скрипта Jenkinsfile от Groovy. Также важно отметить, что всё примеры проверялись на версии 2.414.3 (самый свежий LTS на момент написания статьи) и, возможно, ситуация изменится. Если оно выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, и есть утка. Несмотря на то, что код Jenkinsfile компилируется при помощи Groovy-компилятора, в дальнейшем он подвергается дополнительной обработке и перестаёт вести себя как Groovy-код. То есть он выглядит как Groovy, компилируется как Groovy, но не ведёт себя как Groovy. И речь не о том, что он выполняется в песочнице и часть библиотек не доступна. Всё гораздо хуже: некоторые базовые конструкции тихо меняют своё поведение. Все мы знаем, что Linux великолепен… Он выполняет бесконечные циклы за 5 секунд. Linus Torvalds В Jenkins тело обычного цикла for без условий (классический бесконечный цикл) не выполняется ни разу. Пример проблемного Jenkinsfile (можно также выполнить в https://groovyide.com/playground): Вывод в https://groovyide.com/playground: Вывод в Jenkins: Нарушенная спецификация: https://groovy-lang.org/semantics.html#_classic_for_loop https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.14-110 https://docs.oracle.com/javase/tutorial/java/nutsandbolts/for.html В Jenkins по-другому работает вызов методов в стиле обращения к свойствам объекта. Пример проблемного Jenkinsfile (можно также выполнить в https://groovyide.com/playground): Вывод в https://groovyide.com/playground: Вывод в Jenkins: Нарушенная спецификация: https://groovy-lang.org/objectorientation.html#_property_naming_conventions После данных примеров особо хотел бы отметить: Это далеко не исчерпывающий список проблем. В реальности всё гораздо хуже, просто эти два примера очень легко воспроизвести. Код в случае подобных проблем меняет своё поведение тихо, а не выплёвывает ошибку. Это добавляет отдельной остроты в поиске проблем. Очень тяжело писать код, когда ни в какой строке нельзя быть уверенным. Причем это касается не только самого Jenkinsfile, но и кода в Jenkins Shared Libraries. Хотя с Jenkins Shared Library немного проще – там можно писать код который не обладает подобными эффектами. Причина изменения поведения кода – Continuation Passing Style (CPS) преобразование. Jenkins преобразовывает уже скомпилированный в байт-код скрипт к виду, когда может выполнять его по шагам сохраняя внутренне состояние отдельно. На этапе этого преобразования некоторые конструкции меняют своё поведение. Лучше всего избегать сложной логики в Jenkinsfile, но это не всегда возможно. К счастью, есть проект JenkinsPipelineUnit, который позволяет из Unit-тестов на настоящем Groovy выполнять код после CPS-преобразования. Этот проект позволяет писать тесты на код, выполняемый в скриптах Jenkins, но я так и не смог найти красивое решение по организации тестируемого кода в Jenkinfile. Общий механизм написания тестируемого кода у меня получился примерно следующий: весь тестируемый код оформляется в Jenkins Shared Libraries на тестируемый код пишутся тесты с помощью JenkinsPipelineUnit при использования этого кода в Jenkinsfile, для подключения Jenkins Shared Library из того же репозитория использую метод library Об использовании Jenkins Shared Libraries в том же репозитории много написано на StackOverflow: https://stackoverflow.com/questions/46213913/load-jenkins-pipeline-shared-library-from-same-repository Суть проблемы в том, что через @Library нельзя сослаться на тот же коммит того же репозитория. В результате приходится загружать библиотеку динамически кодом вида: Если собрать всё это вместе, то оно работает, но результат выглядит так себе: в Jenkinsfile можно обращаться к статическим методам, но нельзя сослаться на типы; код Jenkins Shared Libraries лежит вперемешку с основным кодом репозитория. Посмотреть репозиторий с запусков тестов для Jenkins-скриптов можно здесь: https://github.com/bozaro/jenkins-testing В целом, каждый раз, когда речь заходит о Jenkins, я вспоминаю цитату из твита: «Jenkins is currently the CI gold standard and it’s a very low bar».  Программист