Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
df8fa0a
SRE-322 ci: Support multi-line Test-tag commit pragmas
phender Mar 12, 2024
712833e
SRE-322 ci: fix the behavior of adding multiple 'Test-tags'
osalyk May 12, 2026
abc25ef
fix test
osalyk May 12, 2026
8c01c78
improve getFunctionalTags.groovy
osalyk May 12, 2026
50657bc
fix pragmasToMap.groovy file
osalyk May 12, 2026
36941b2
fix envToPragmas.groovy
osalyk May 12, 2026
c5da349
add changes to selfUnitTest.groovy
osalyk May 12, 2026
fa956e7
fix getFunctionalTags.groovy
osalyk May 12, 2026
fa3c1aa
Final fix
osalyk May 12, 2026
604dcda
standardize the type
osalyk May 12, 2026
69ca32c
update parseStageInfo.groovy
osalyk May 12, 2026
efcc76c
improve commitPragma.groovy
osalyk May 12, 2026
096ed49
Modify parseStageInfo.groovy
osalyk May 12, 2026
db0626c
fix the syntax
osalyk May 13, 2026
82a20be
test: add first test to verify testTag
osalyk May 13, 2026
46ee7ba
remove the old dependency
osalyk May 14, 2026
3811d95
Merge remote-tracking branch 'origin/master' into osalyk/SRE-322_new-fix
osalyk May 14, 2026
1f12959
Basic integration test - IMHO too complex but useful for debug for now
janekmi May 14, 2026
807850b
Necessary fix!
janekmi May 14, 2026
b8984c2
fix assert order
janekmi May 14, 2026
675dcfe
Add debug prints
janekmi May 14, 2026
f84767f
fix envToPragmas file
osalyk May 15, 2026
411d495
Use your branch
osalyk May 15, 2026
81bc236
Replace “Matcher” with “String”
osalyk May 15, 2026
f4f79be
Fix gradle test
osalyk May 15, 2026
f34ad81
improve envToPragmas
osalyk May 15, 2026
f7117a4
Merge remote-tracking branch 'origin/master' into osalyk/SRE-322_new-fix
osalyk May 18, 2026
adafff9
Restore the use of the master branch
osalyk May 19, 2026
16b5120
edit envToPragmas.groovy
osalyk May 19, 2026
85ce979
Work on a local copy in envToPragmas
osalyk May 19, 2026
a09fc16
edit envToPragmas file
osalyk May 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
// That PR should be landed with out deleting the PR branch.
// Then a second PR submitted to comment out the @Library line, and when it
// is landed, both PR branches can be deleted.
//@Library(value='pipeline-lib@my_branch_name') _
@Library(value='pipeline-lib@osalyk/SRE-322_new-fix') _

/* groovylint-disable-next-line CompileStatic */
job_status_internal = [:]
Expand Down Expand Up @@ -612,7 +612,13 @@ pipeline {
'full_regression,foobar,@stages.tag@'],
[tags: [[tag: 'Test-tag', value: 'datamover foobar']],
tag_template: 'datamover,@stages.tag@ foobar,@stages.tag@'],
/* this one doesn't quite work due to the @commits.value@ substitution
[tags: [[tag: 'Test-tag', value: 'line1'],
[tag: 'Test-tag', value: 'line2'],
[tag: 'Test-tag', value: 'line3'],
[tag: 'Test-tag', value: 'line4'],],
tag_template: 'line1,@stages.tag@ line2,@stages.tag@ ' +
'line3,@stages.tag@ line4,@stages.tag@'],
/* this one doesn't quite work due to the @commits.value@ substitution
not accounting for the skip-list
[tags: [[tag: 'Test-tag', value: 'datamover'],
[tag: 'Features', value: 'foobar'],
Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dependencies {
implementation localGroovy()
testImplementation localGroovy()
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
testImplementation 'org.spockframework:spock-core:2.4-groovy-4.0'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.0'
}

Expand Down
146 changes: 146 additions & 0 deletions src/test/groovy/checkTestTags.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Copyright 2026 Hewlett Packard Enterprise Development LP
*
* SPDX-License-Identifier: BSD-2-Clause-Patent
*/

package com.daos.pipeline

import static org.junit.jupiter.api.Assertions.*

import spock.lang.Specification

class TagTemplateSpec extends Specification {

static final String stage_name = 'Functional on Leap 15'

private Script loadScript(String script, Map extraBinding = [:]) {
Binding binding = new Binding()

// override bindings as required for a specific test
extraBinding.each { k, v ->
binding.setVariable(k, v)
}

GroovyShell shell = new GroovyShell(binding)
return shell.parse(new File("vars/${script}.groovy"))
}

private Script loadPragmasToEnv() {
Closure pragmasToMapWrap = { String commit_message ->
Script pragmasToMap = loadScript('pragmasToMap')
return pragmasToMap.call(commit_message)
}

Map extraBinding = [
pragmasToMap: pragmasToMapWrap,
env: [:]
]

return loadScript('pragmasToEnv', extraBinding)
}

private Script loadParseStageInfo(Map extraBinding) {
extraBinding.distroVersion = { String distro ->
return 'XXA'
}
extraBinding.cachedCommitPragma = { String a, String b ->
return 'XXB'
}
extraBinding.paramsValue = { String a, String b ->
return 'XXC'
}
extraBinding.error = { String a ->}
extraBinding.getPragmaSuffix = {-> return 'XXD' }

Closure getFunctionalStageTagsWrap = {
->
Script getFunctionalStageTags = loadScript('getFunctionalStageTags', [
env: extraBinding.env,
])
return getFunctionalStageTags.call()
}
extraBinding.getFunctionalStageTags = getFunctionalStageTagsWrap
extraBinding.startedByTimer = {-> return false }
extraBinding.branchTypeRE = { String a -> return 'XXE' }

Closure getPragmaSuffixWrap = {
->
Script getPragmaSuffix = loadScript('getPragmaSuffix', [
env: extraBinding.env,
])
return getPragmaSuffix.call()
}
Closure envToPragmasWrap = {
->
Script envToPragmas = loadScript('envToPragmas', [
env: extraBinding.env,
])
return envToPragmas.call()
}
Closure commitPragmaWrap = {String a, String b ->
Script commitPragma = loadScript('commitPragma', [
env: extraBinding.env,
envToPragmas: envToPragmasWrap
])
return commitPragma.call(a, b)
}
extraBinding.getFunctionalTags = { Map kwargs ->
Script getFunctionalTags = loadScript('getFunctionalTags', [
getPragmaSuffix: getPragmaSuffixWrap,
getFunctionalStageTags: getFunctionalStageTagsWrap,
startedByUser: {-> return false },
startedByUpstream: {-> return false },
startedByTimer: {-> return false },
commitPragma: commitPragmaWrap,
getSkippedTests: {
-> return []
}
])
return getFunctionalTags.call(kwargs)
}
extraBinding.getFunctionalArgs = { LinkedHashMap a -> return [:] }
return loadScript('parseStageInfo', extraBinding)
}

/**
* Replaces successive occurrences of the placeholder with successive tag values.
* This mirrors the behavior required by the example: each @stages.tag@ is consumed
* in order and replaced by the corresponding tags[i].value.
*/
private String expandTagTemplate(List<Map> tags) {
String cm = '''\
Test commit\n'''
tags.each { tag ->
cm += """\

${tag.tag}: ${tag.value}"""
}
Script pragmasToEnv = loadPragmasToEnv()
String tmp_pragmas = pragmasToEnv.call(cm.stripIndent())
Map env = [
STAGE_NAME: stage_name,
UNIT_TEST: 'true',
pragmas: tmp_pragmas,
COMMIT_MESSAGE: cm.stripIndent(),
RELEASE_BRANCH: 'master'
]
Script parseStageInfo = loadParseStageInfo([env: env])
Map info = parseStageInfo.call()
return info['test_tag']
}

def "expand tag_template by replacing @stages.tag@ with tag values in order"() {
given: "input tags and tag_template as in the example"
def tags = [
[tag: 'Test-tag', value: 'line1'],
[tag: 'Test-tag', value: 'line2'],
[tag: 'Test-tag', value: 'line3'],
[tag: 'Test-tag', value: 'line4'],
]
def tagTemplate = 'line1,vm line2,vm line3,vm line4,vm'

def result = expandTagTemplate(tags)
assertEquals(tagTemplate, result)
}
}
35 changes: 29 additions & 6 deletions vars/commitPragma.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,35 @@ String call(String name, String def_val = null) {
if (env.pragmas) {
Map pragmas = envToPragmas()

if (pragmas[name.toLowerCase()]) {
return pragmas[name.toLowerCase()]
} else if (def_val) {
return def_val
String key = name.toLowerCase()
def value = pragmas[key]

if (key == 'test-tag') {
if (value instanceof List) {
return value.join(' ')
}
if (value != null) {
return value.toString()
}
return def_val ?: ''
}

if (value instanceof List) {
return value.join(' ')
}
if (value != null) {
return value.toString()
}
return ''
return def_val ?: ''
}
return commitPragmaTrusted(name, def_val)

// fallback: trusted source
if (name.toLowerCase() == 'test-tag') {
def trusted = commitPragmaTrusted(name, def_val)
return (trusted instanceof List) ? trusted.join(' ') : (trusted ?: def_val ?: '')
}

def trusted = commitPragmaTrusted(name, def_val)
return (trusted instanceof List) ? trusted.join(' ') : (trusted ?: def_val ?: '')
}

113 changes: 107 additions & 6 deletions vars/envToPragmas.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,114 @@
*/

Map call() {
Map pragmas = [:]
if (env.pragmas) {
pragmas = "${env.pragmas}"[1..-2].split(', ').collectEntries { entry ->
String[] pair = entry.split('=', 2)
[(pair.first()): pair.last()]
// Work on a local copy to avoid storing non-serializable objects in env
if (!env.pragmas) {
return [:]
}

def raw = env.pragmas

// If someone accidentally put a Matcher into env.pragmas, convert it immediately
if (raw instanceof java.util.regex.Matcher) {
raw = raw.find() ? raw.group(0) : raw.toString()
}

// If already a Map, normalize keys and ensure list types for test-tag
if (raw instanceof Map) {
Map m = [:]
((Map) raw).each { k, v ->
m[k.toString().toLowerCase()] = v
}
if (m['test-tag'] != null && !(m['test-tag'] instanceof List)) {
m['test-tag'] = safeUtils.ensureList(m['test-tag'])
}
return m
}

return pragmas
// Convert raw to a trimmed String representation for parsing
String s = (raw instanceof String) ? raw.trim() :
(raw instanceof List) ? raw.collect { it?.toString()?.trim() }.join(',') :
raw?.toString()?.trim()

if (!s) {
return [:]
}

try {
// 1) Form like: [test-tag:[line1, line2, line3]]
def m1 = (s =~ /^\s*
\[([^\:
\[\]
]+)\s*:\s*
\[([^\]
]*)\]
\s*\]
$/)

if (m1.matches()) {
def rawKey = m1[0][1]
String key = rawKey?.toString()?.trim()?.toLowerCase() ?: ''
def innerVal = m1[0][2]
String inner = innerVal?.toString()?.trim() ?: ''
List values = inner ? inner.split(/\s*,\s*/).collect { it?.toString()?.trim() } : []
return [(key): values]
}

// 2) Generic map-like forms: {k=v, k2=[a,b], k3:val}
String body = s
if ((body.startsWith('{') && body.endsWith('}')) || (body.startsWith('[') && body.endsWith(']'))) {
body = (body.length() > 2) ? body[1..-2].trim() : ''
}

Map pragmasMap = [:]
def parts = []
int depth = 0
StringBuilder cur = new StringBuilder()
body.each { ch ->
if (ch == '[' || ch == '{') { depth++ }
if (ch == ']' || ch == '}') { depth-- }
if (ch == ',' && depth == 0) {
parts << cur.toString()
cur.setLength(0)
} else {
cur.append(ch)
}
}
if (cur.length() > 0) { parts << cur.toString() }

parts.each { entry ->
if (!entry) return
def kv = entry.split(/[:=]/, 2)
if (kv.length == 0) return
String key = kv[0]?.toString()?.trim()?.toLowerCase() ?: ''
String rawVal = (kv.length > 1) ? kv[1] : ''

if (rawVal instanceof List) {
pragmasMap[key] = rawVal.collect { it?.toString()?.trim() }
} else {
String rawValStr = rawVal?.toString()?.trim() ?: ''
String rv = rawValStr
if (rv.startsWith('[') && rv.endsWith(']')) {
String inner = (rv.length() > 2) ? rv[1..-2].trim() : ''
List values = inner ? inner.split(/\s*,\s*/).collect { it?.toString()?.trim() } : []
pragmasMap[key] = values
} else {
String scalar = rv
if ((scalar.startsWith('"') && scalar.endsWith('"')) || (scalar.startsWith("'") && scalar.endsWith("'"))) {
scalar = scalar[1..-2]
}
pragmasMap[key] = scalar.trim()
}
}
}

// Ensure test-tag is a list
if (pragmasMap['test-tag'] != null && !(pragmasMap['test-tag'] instanceof List)) {
pragmasMap['test-tag'] = safeUtils.ensureList(pragmasMap['test-tag'])
}

return pragmasMap
} catch (Exception e) {
return [:]
}
}
16 changes: 11 additions & 5 deletions vars/getFunctionalTags.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
* default_tags launch.py tag argument to use when no parameter or commit pragma tags exist
* @return String of test tags to run in the stage
*/
Map call(Map kwargs = [:]) {
String call(Map kwargs = [:]) {
/* groovylint-disable-next-line UnnecessaryGetter */
String pragma_suffix = kwargs.get('pragma_suffix', getPragmaSuffix())
/* groovylint-disable-next-line UnnecessaryGetter */
String stage_tags = kwargs.get('stage_tags', getFunctionalStageTags())
String default_tags = kwargs.get('default_tags', 'pr')
/* default_tags always treated as String (commitPragma already normalizes) */
String default_tags = kwargs.get('default_tags', 'pr')?.toString()
String requested_tags = ''

// Define the test tags to use in this stage
Expand All @@ -30,10 +31,15 @@ Map call(Map kwargs = [:]) {
requested_tags = default_tags
}
// Builds started from a commit should first use any commit pragma 'Test-tag*:' tags if defined
requested_tags = requested_tags ?: commitPragma('Test-tag' + pragma_suffix, commitPragma('Test-tag', ''))
if (!requested_tags) {
requested_tags = commitPragma('Test-tag' + pragma_suffix,
commitPragma('Test-tag', ''))
}

// Builds started from a commit should finally use the default tags for the stage
requested_tags = requested_tags ?: default_tags
// Fallback to default tags
if (!requested_tags) {
requested_tags = default_tags
}

// Append any commit pragma 'Features:' tags if defined
String features = commitPragma('Features', '')
Expand Down
Loading