From df8fa0af6944ce90bf35a994738548775d24ea07 Mon Sep 17 00:00:00 2001 From: Phil Henderson Date: Tue, 12 Mar 2024 08:58:31 -0400 Subject: [PATCH 01/29] SRE-322 ci: Support multi-line Test-tag commit pragmas Adding unit test for specifying the Test-tag commit pragma on multiple lines and expecting the values to be concatenated. Skip-daos-build-and-test: true Signed-off-by: Phil Henderson --- Jenkinsfile | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 0a5f1cb39..7f0a7c3f4 100755 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -612,6 +612,7 @@ pipeline { 'full_regression,foobar,@stages.tag@'], [tags: [[tag: 'Test-tag', value: 'datamover foobar']], tag_template: 'datamover,@stages.tag@ foobar,@stages.tag@'], +<<<<<<< HEAD /* this one doesn't quite work due to the @commits.value@ substitution not accounting for the skip-list [tags: [[tag: 'Test-tag', value: 'datamover'], @@ -622,6 +623,15 @@ pipeline { 'daily_regression,foobar,-test_to_skip,@stages.tag@ ' + 'full_regression,foobar,-test_to_skip,@stages.tag@'] */] commits.eachWithIndex { commit, index -> +======= + [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@']] + commits.each { commit -> +>>>>>>> 7fbf33e (SRE-322 ci: Support multi-line Test-tag commit pragmas) cm = '''\ Test commit\n''' commit.tags.each { tag -> From 712833e6458a6169ed321c44a5bce8781918b35b Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Tue, 12 May 2026 11:08:26 +0200 Subject: [PATCH 02/29] SRE-322 ci: fix the behavior of adding multiple 'Test-tags' Signed-off-by: Oksana Salyk --- Jenkinsfile | 13 ------------- vars/commitPragma.groovy | 13 ++++++++++--- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 7f0a7c3f4..2ce1e30e7 100755 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -612,18 +612,6 @@ pipeline { 'full_regression,foobar,@stages.tag@'], [tags: [[tag: 'Test-tag', value: 'datamover foobar']], tag_template: 'datamover,@stages.tag@ foobar,@stages.tag@'], -<<<<<<< HEAD - /* 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'], - [tag: 'Skip-list', value: 'test_to_skip:DAOS-1234']], - tag_template: '@commits.value@,@stages.tag@ ' + - 'pr,foobar,-test_to_skip,@stages.tag@ ' + - 'daily_regression,foobar,-test_to_skip,@stages.tag@ ' + - 'full_regression,foobar,-test_to_skip,@stages.tag@'] */] - commits.eachWithIndex { commit, index -> -======= [tags: [[tag: 'Test-tag', value: 'line1'], [tag: 'Test-tag', value: 'line2'], [tag: 'Test-tag', value: 'line3'], @@ -631,7 +619,6 @@ pipeline { tag_template: 'line1,@stages.tag@ line2,@stages.tag@' + 'line3,@stages.tag@ line4,@stages.tag@']] commits.each { commit -> ->>>>>>> 7fbf33e (SRE-322 ci: Support multi-line Test-tag commit pragmas) cm = '''\ Test commit\n''' commit.tags.each { tag -> diff --git a/vars/commitPragma.groovy b/vars/commitPragma.groovy index fe9edf76d..04cd4a9d9 100644 --- a/vars/commitPragma.groovy +++ b/vars/commitPragma.groovy @@ -28,9 +28,16 @@ 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) { + String key = name.toLowerCase() + def value = pragmas[key] + + if (key == 'test-tag' && value instanceof List) { + return value.join(' ') + } + if (value) { + return value + } + if (def_val) { return def_val } return '' From abc25ef1e044e7519c4fc485cbac29dbca455935 Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Tue, 12 May 2026 11:21:15 +0200 Subject: [PATCH 03/29] fix test Signed-off-by: Oksana Salyk --- Jenkinsfile | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 2ce1e30e7..e905232a5 100755 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -616,9 +616,18 @@ pipeline { [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@']] - commits.each { commit -> + 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'], + [tag: 'Skip-list', value: 'test_to_skip:DAOS-1234']], + tag_template: '@commits.value@,@stages.tag@ ' + + 'pr,foobar,-test_to_skip,@stages.tag@ ' + + 'daily_regression,foobar,-test_to_skip,@stages.tag@ ' + + 'full_regression,foobar,-test_to_skip,@stages.tag@'] */] + commits.eachWithIndex { commit, index -> cm = '''\ Test commit\n''' commit.tags.each { tag -> From 8c01c78738d18e8503a7755ee666d71f6f52365d Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Tue, 12 May 2026 11:43:34 +0200 Subject: [PATCH 04/29] improve getFunctionalTags.groovy Signed-off-by: Oksana Salyk --- vars/getFunctionalTags.groovy | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/vars/getFunctionalTags.groovy b/vars/getFunctionalTags.groovy index 25ec16ceb..46d57dbf2 100644 --- a/vars/getFunctionalTags.groovy +++ b/vars/getFunctionalTags.groovy @@ -30,7 +30,23 @@ 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) { + // Collect all Test-tag pragmas (multiple allowed) + def tags = [] + + // With suffix + def t1 = commitPragma('Test-tag' + pragma_suffix, null) + if (t1 instanceof List) { + tags.addAll(t1) + } else if (t1) { + tags << t1 + } + + // Without suffix + def t2 = commitPragma('Test-tag', null) + if (t2 instanceof List) tags.addAll(t2) else if (t2) tags << t2 + requested_tags = tags ? tags.join(' ') : '' + } // Builds started from a commit should finally use the default tags for the stage requested_tags = requested_tags ?: default_tags From 50657bc45d70a103ebfb8682efc3a5dcaf354834 Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Tue, 12 May 2026 11:57:01 +0200 Subject: [PATCH 05/29] fix pragmasToMap.groovy file Signed-off-by: Oksana Salyk --- vars/pragmasToMap.groovy | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/vars/pragmasToMap.groovy b/vars/pragmasToMap.groovy index edfb3ceeb..d75a9bd2f 100644 --- a/vars/pragmasToMap.groovy +++ b/vars/pragmasToMap.groovy @@ -22,7 +22,23 @@ Map call(String commit_message) { // this returns from the .each closure, not the method return } - pragmas[key.toLowerCase()] = value.trim() + String k = key.toLowerCase() + String v = value.trim() + + // Special handling: allow multiple Test-tag pragmas + if (k == 'test-tag') { + def existing = pragmas[k] + if (existing == null) { + pragmas[k] = [v] + } else if (existing instanceof List) { + pragmas[k] << v + } else { + pragmas[k] = [existing, v] + } + } else { + // default behavior for all other pragmas + pragmas[k] = v + } /* groovylint-disable-next-line CatchArrayIndexOutOfBoundsException */ } catch (ArrayIndexOutOfBoundsException ignored) { // ignore and move on to the next line From 36941b2eb2fa56d0fb257f0f936f7f66f2f05a27 Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Tue, 12 May 2026 12:04:29 +0200 Subject: [PATCH 06/29] fix envToPragmas.groovy Signed-off-by: Oksana Salyk --- vars/envToPragmas.groovy | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/vars/envToPragmas.groovy b/vars/envToPragmas.groovy index b9aade884..82a7988a1 100644 --- a/vars/envToPragmas.groovy +++ b/vars/envToPragmas.groovy @@ -8,12 +8,18 @@ */ Map call() { + if (!env.pragmas) { + return [:] + } + + if (env.pragmas instanceof Map) { + return (Map) env.pragmas + } + Map pragmas = [:] - if (env.pragmas) { - pragmas = "${env.pragmas}"[1..-2].split(', ').collectEntries { entry -> - String[] pair = entry.split('=', 2) - [(pair.first()): pair.last()] - } + pragmas = "${env.pragmas}"[1..-2].split(', ').collectEntries { entry -> + String[] pair = entry.split('=', 2) + [(pair.first()): pair.last()] } return pragmas From c5da349105aa528858e25f7e52631b381eea2078 Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Tue, 12 May 2026 12:19:57 +0200 Subject: [PATCH 07/29] add changes to selfUnitTest.groovy Signed-off-by: Oksana Salyk --- vars/selfUnitTest.groovy | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vars/selfUnitTest.groovy b/vars/selfUnitTest.groovy index 5be1eff70..b788a5eda 100644 --- a/vars/selfUnitTest.groovy +++ b/vars/selfUnitTest.groovy @@ -95,6 +95,11 @@ Signed-off-by: Brian J. Murrell ''' println(" expected_map = ${expected_map}") assert(result_map == expected_map) + println "env.pragmas = ${env.pragmas} (${env.pragmas?.getClass()})" + println "envToPragmas()['test-tag'] = ${envToPragmas()['test-tag']} (${envToPragmas()['test-tag']?.getClass()})" + println "commitPragma('Test-tag') = ${commitPragma('Test-tag')} (${commitPragma('Test-tag')?.getClass()})" + println "parseStageInfo()['test_tag'] = ${parseStageInfo()['test_tag']}" + println(' with overwrite=false') updatePragmas('Test-tag: should_not_overwrite', false) result_map = envToPragmas() From fa956e7b83a98834414a0c67668cf886a39c5e1e Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Tue, 12 May 2026 12:20:39 +0200 Subject: [PATCH 08/29] fix getFunctionalTags.groovy Signed-off-by: Oksana Salyk --- vars/getFunctionalTags.groovy | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/vars/getFunctionalTags.groovy b/vars/getFunctionalTags.groovy index 46d57dbf2..5a3248278 100644 --- a/vars/getFunctionalTags.groovy +++ b/vars/getFunctionalTags.groovy @@ -17,7 +17,15 @@ Map call(Map kwargs = [:]) { 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 may be a List (from multiple Test-tag pragmas) + def raw_default_tags = kwargs.get('default_tags', 'pr') + String default_tags + if (raw_default_tags instanceof List) { + // join list into a single space-separated string + default_tags = raw_default_tags.join(' ') + } else { + default_tags = raw_default_tags + } String requested_tags = '' // Define the test tags to use in this stage @@ -31,21 +39,12 @@ Map call(Map kwargs = [:]) { } // Builds started from a commit should first use any commit pragma 'Test-tag*:' tags if defined if (!requested_tags) { - // Collect all Test-tag pragmas (multiple allowed) - def tags = [] - - // With suffix - def t1 = commitPragma('Test-tag' + pragma_suffix, null) - if (t1 instanceof List) { - tags.addAll(t1) - } else if (t1) { - tags << t1 + def t = commitPragma('Test-tag' + pragma_suffix, commitPragma('Test-tag', '')) + if (t instanceof List) { + requested_tags = t.join(' ') + } else { + requested_tags = t ?: '' } - - // Without suffix - def t2 = commitPragma('Test-tag', null) - if (t2 instanceof List) tags.addAll(t2) else if (t2) tags << t2 - requested_tags = tags ? tags.join(' ') : '' } // Builds started from a commit should finally use the default tags for the stage From fa3c1aa8e8bcfad99f1ad9f79a6d2148bd2a54f6 Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Tue, 12 May 2026 12:34:03 +0200 Subject: [PATCH 09/29] Final fix Signed-off-by: Oksana Salyk --- vars/getFunctionalTags.groovy | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vars/getFunctionalTags.groovy b/vars/getFunctionalTags.groovy index 5a3248278..f36b6a4df 100644 --- a/vars/getFunctionalTags.groovy +++ b/vars/getFunctionalTags.groovy @@ -23,6 +23,8 @@ Map call(Map kwargs = [:]) { if (raw_default_tags instanceof List) { // join list into a single space-separated string default_tags = raw_default_tags.join(' ') + } else if (raw_default_tags instanceof String && raw_default_tags.contains(',')) { + default_tags = raw_default_tags } else { default_tags = raw_default_tags } @@ -48,7 +50,9 @@ Map call(Map kwargs = [:]) { } // Builds started from a commit should finally use the default tags for the stage - requested_tags = requested_tags ?: default_tags + if (!requested_tags) { + requested_tags = default_tags + } // Append any commit pragma 'Features:' tags if defined String features = commitPragma('Features', '') From 604dcda7b7fb0976a7c7163b090da472ee17551c Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Tue, 12 May 2026 13:00:47 +0200 Subject: [PATCH 10/29] standardize the type Signed-off-by: Oksana Salyk --- vars/commitPragma.groovy | 13 +++++-------- vars/getFunctionalTags.groovy | 23 +++++------------------ 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/vars/commitPragma.groovy b/vars/commitPragma.groovy index 04cd4a9d9..3d7b2ac09 100644 --- a/vars/commitPragma.groovy +++ b/vars/commitPragma.groovy @@ -30,17 +30,14 @@ String call(String name, String def_val = null) { String key = name.toLowerCase() def value = pragmas[key] - - if (key == 'test-tag' && value instanceof List) { + + if (value instanceof List) { return value.join(' ') } - if (value) { - return value - } - if (def_val) { - return def_val + if (value != null) { + return value.toString() } - return '' + return def_val ?: '' } return commitPragmaTrusted(name, def_val) } diff --git a/vars/getFunctionalTags.groovy b/vars/getFunctionalTags.groovy index f36b6a4df..38efaf128 100644 --- a/vars/getFunctionalTags.groovy +++ b/vars/getFunctionalTags.groovy @@ -17,17 +17,8 @@ Map call(Map kwargs = [:]) { String pragma_suffix = kwargs.get('pragma_suffix', getPragmaSuffix()) /* groovylint-disable-next-line UnnecessaryGetter */ String stage_tags = kwargs.get('stage_tags', getFunctionalStageTags()) - // default_tags may be a List (from multiple Test-tag pragmas) - def raw_default_tags = kwargs.get('default_tags', 'pr') - String default_tags - if (raw_default_tags instanceof List) { - // join list into a single space-separated string - default_tags = raw_default_tags.join(' ') - } else if (raw_default_tags instanceof String && raw_default_tags.contains(',')) { - default_tags = raw_default_tags - } else { - default_tags = raw_default_tags - } + /* 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 @@ -41,15 +32,11 @@ Map call(Map kwargs = [:]) { } // Builds started from a commit should first use any commit pragma 'Test-tag*:' tags if defined if (!requested_tags) { - def t = commitPragma('Test-tag' + pragma_suffix, commitPragma('Test-tag', '')) - if (t instanceof List) { - requested_tags = t.join(' ') - } else { - requested_tags = t ?: '' - } + requested_tags = commitPragma('Test-tag' + pragma_suffix, + commitPragma('Test-tag', '')) } - // Builds started from a commit should finally use the default tags for the stage + // Fallback to default tags if (!requested_tags) { requested_tags = default_tags } From 69ca32ceda0b5209829d6243ac20d68fb0b74c11 Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Tue, 12 May 2026 13:09:34 +0200 Subject: [PATCH 11/29] update parseStageInfo.groovy Signed-off-by: Oksana Salyk --- vars/parseStageInfo.groovy | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/vars/parseStageInfo.groovy b/vars/parseStageInfo.groovy index bf7719c60..8b50956d3 100755 --- a/vars/parseStageInfo.groovy +++ b/vars/parseStageInfo.groovy @@ -246,7 +246,13 @@ Map call(Map config = [:]) { Map kwargs = [:] kwargs['pragma_suffix'] = result['pragma_suffix'] kwargs['stage_tags'] = getFunctionalStageTags() - kwargs['default_tags'] = config['test_tag'] + def dt = config['test_tag'] + if (dt instanceof List) { + kwargs['default_tags'] = dt.join(' ') + } else { + kwargs['default_tags'] = dt ?: '' + } + if (!kwargs['default_tags']) { if (startedByTimer() && env.BRANCH_NAME =~ branchTypeRE('weekly')) { kwargs['default_tags'] = 'full_regression' From efcc76c2c3fc6356a9542b7c3aeccbb44624627c Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Tue, 12 May 2026 13:49:43 +0200 Subject: [PATCH 12/29] improve commitPragma.groovy Signed-off-by: Oksana Salyk --- vars/commitPragma.groovy | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/vars/commitPragma.groovy b/vars/commitPragma.groovy index 3d7b2ac09..8a297d2fa 100644 --- a/vars/commitPragma.groovy +++ b/vars/commitPragma.groovy @@ -30,6 +30,16 @@ String call(String name, String def_val = null) { 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(' ') @@ -39,5 +49,11 @@ String call(String name, String def_val = null) { } return def_val ?: '' } - return commitPragmaTrusted(name, def_val) + + if (name.toLowerCase() == 'test-tag') { + return def_val ?: '' + } + + def trusted = commitPragmaTrusted(name, def_val) + return (trusted instanceof List) ? trusted.join(' ') : trusted } From 096ed494f75eddb0acab17042123a5fc2e46b102 Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Tue, 12 May 2026 14:37:16 +0200 Subject: [PATCH 13/29] Modify parseStageInfo.groovy Signed-off-by: Oksana Salyk --- vars/parseStageInfo.groovy | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/vars/parseStageInfo.groovy b/vars/parseStageInfo.groovy index 8b50956d3..ca7a0660c 100755 --- a/vars/parseStageInfo.groovy +++ b/vars/parseStageInfo.groovy @@ -215,20 +215,21 @@ Map call(Map config = [:]) { result['test'] = 'Functional' result['node_count'] = 9 result['always_script'] = config.get('always_script', 'ci/functional/job_cleanup.sh') + if (stage_name.contains('Hardware')) { ftest_arg_nvme = get_default_nvme() + if (stage_name.contains('Small')) { result['node_count'] = 3 } else if (stage_name.contains('Medium')) { result['node_count'] = 5 + if (stage_name.contains('Provider')) { if (stage_name.contains('Verbs')) { ftest_arg_provider = 'ofi+verbs' - } - else if (stage_name.contains('UCX')) { + } else if (stage_name.contains('UCX')) { ftest_arg_provider = 'ucx+dc_x' - } - else if (stage_name.contains('TCP')) { + } else if (stage_name.contains('TCP')) { ftest_arg_provider = 'ofi+tcp' } } @@ -236,22 +237,44 @@ Map call(Map config = [:]) { result['node_count'] = 24 } } + if (stage_name.contains('with Valgrind')) { result['with_valgrind'] = 'memcheck' config['test_tag'] = 'memcheck' } + result['pragma_suffix'] = getPragmaSuffix() // Get the ftest tags Map kwargs = [:] kwargs['pragma_suffix'] = result['pragma_suffix'] kwargs['stage_tags'] = getFunctionalStageTags() + def dt = config['test_tag'] + + if (!dt && config['tags'] instanceof List) { + def testTagValues = config['tags'] + .findAll { it.tag?.toLowerCase() == 'test-tag' } + .collect { it.value?.toString() } + + if (testTagValues && !testTagValues.isEmpty()) { + dt = testTagValues.join(' ') + } + } + if (dt instanceof List) { kwargs['default_tags'] = dt.join(' ') } else { kwargs['default_tags'] = dt ?: '' } + } + + if (dt instanceof List) { + kwargs['default_tags'] = dt.join(' ') + } else { + kwargs['default_tags'] = dt ?: '' + } + if (!kwargs['default_tags']) { if (startedByTimer() && env.BRANCH_NAME =~ branchTypeRE('weekly')) { From db0626cfaead0451f1102b3d59482b9a933248cc Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Wed, 13 May 2026 13:17:44 +0200 Subject: [PATCH 14/29] fix the syntax Signed-off-by: Oksana Salyk --- vars/commitPragma.groovy | 10 ++++++---- vars/parseStageInfo.groovy | 18 +++--------------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/vars/commitPragma.groovy b/vars/commitPragma.groovy index 8a297d2fa..08691520a 100644 --- a/vars/commitPragma.groovy +++ b/vars/commitPragma.groovy @@ -27,7 +27,6 @@ String call(Map config = [:]) { String call(String name, String def_val = null) { if (env.pragmas) { Map pragmas = envToPragmas() - String key = name.toLowerCase() def value = pragmas[key] @@ -40,7 +39,7 @@ String call(String name, String def_val = null) { } return def_val ?: '' } - + if (value instanceof List) { return value.join(' ') } @@ -50,10 +49,13 @@ String call(String name, String def_val = null) { return def_val ?: '' } + // fallback: trusted source if (name.toLowerCase() == 'test-tag') { - return def_val ?: '' + 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 + return (trusted instanceof List) ? trusted.join(' ') : (trusted ?: def_val ?: '') } + diff --git a/vars/parseStageInfo.groovy b/vars/parseStageInfo.groovy index ca7a0660c..fd31537d1 100755 --- a/vars/parseStageInfo.groovy +++ b/vars/parseStageInfo.groovy @@ -250,31 +250,19 @@ Map call(Map config = [:]) { kwargs['pragma_suffix'] = result['pragma_suffix'] kwargs['stage_tags'] = getFunctionalStageTags() + // przed użyciem kwargs def dt = config['test_tag'] if (!dt && config['tags'] instanceof List) { def testTagValues = config['tags'] .findAll { it.tag?.toLowerCase() == 'test-tag' } .collect { it.value?.toString() } - if (testTagValues && !testTagValues.isEmpty()) { - dt = testTagValues.join(' ') + dt = testTagValues } } - if (dt instanceof List) { - kwargs['default_tags'] = dt.join(' ') - } else { - kwargs['default_tags'] = dt ?: '' - } - } - - if (dt instanceof List) { - kwargs['default_tags'] = dt.join(' ') - } else { - kwargs['default_tags'] = dt ?: '' - } - + kwargs['default_tags'] = (dt instanceof List) ? dt : (dt ? [dt.toString()] : []) if (!kwargs['default_tags']) { if (startedByTimer() && env.BRANCH_NAME =~ branchTypeRE('weekly')) { From 82a20bed80d3fb14b3fbfb0d30e8432f94728ef0 Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Wed, 13 May 2026 13:20:57 +0200 Subject: [PATCH 15/29] test: add first test to verify testTag Signed-off-by: Oksana Salyk --- build.gradle | 2 ++ src/test/groovy/checkTestTags.groovy | 52 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 src/test/groovy/checkTestTags.groovy diff --git a/build.gradle b/build.gradle index 93df49941..f1ab8e96a 100644 --- a/build.gradle +++ b/build.gradle @@ -17,6 +17,8 @@ dependencies { implementation localGroovy() testImplementation localGroovy() testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' + testImplementation 'org.spockframework:spock-core:2.3-groovy-3.0' + testImplementation 'org.codehaus.groovy:groovy-all:3.0.17' testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.0' } diff --git a/src/test/groovy/checkTestTags.groovy b/src/test/groovy/checkTestTags.groovy new file mode 100644 index 000000000..45c7af543 --- /dev/null +++ b/src/test/groovy/checkTestTags.groovy @@ -0,0 +1,52 @@ +/* + * Copyright 2026 Hewlett Packard Enterprise Development LP + * + * SPDX-License-Identifier: BSD-2-Clause-Patent + */ + +package com.daos.pipeline + +import spock.lang.Specification + +class TagTemplateSpec extends Specification { + + 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,@stages.tag@ line2,@stages.tag@ ' + + 'line3,@stages.tag@ line4,@stages.tag@' + + when: "we expand the template by replacing each @stages.tag@ with the next tag.value" + def result = expandTagTemplate(tagTemplate, tags) + + then: "the result matches the expected expansion" + result == 'line1,line1 line2,line2 line3,line3 line4,line4' + } + + /** + * 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(String template, List tags) { + def placeholder = '@stages.tag@' + def sb = new StringBuilder(template) + int searchFrom = 0 + tags.each { t -> + int idx = sb.indexOf(placeholder, searchFrom) + if (idx == -1) { + // no more placeholders; stop replacing + return sb.toString() + } + sb.replace(idx, idx + placeholder.length(), t.value) + // move searchFrom past the inserted value to avoid re-matching inside it + searchFrom = idx + t.value.length() + } + return sb.toString() + } +} From 46ee7ba32a7e12eda2085bc5afaf533653c949a0 Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Thu, 14 May 2026 12:40:20 +0200 Subject: [PATCH 16/29] remove the old dependency Signed-off-by: Oksana Salyk --- build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index f1ab8e96a..2b267d4dd 100644 --- a/build.gradle +++ b/build.gradle @@ -17,8 +17,7 @@ dependencies { implementation localGroovy() testImplementation localGroovy() testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' - testImplementation 'org.spockframework:spock-core:2.3-groovy-3.0' - testImplementation 'org.codehaus.groovy:groovy-all:3.0.17' + testImplementation 'org.spockframework:spock-core:2.4-groovy-4.0' testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.0' } From 1f12959a94a51abe64463629cabe52cac680224c Mon Sep 17 00:00:00 2001 From: Jan Michalski Date: Thu, 14 May 2026 13:02:55 +0000 Subject: [PATCH 17/29] Basic integration test - IMHO too complex but useful for debug for now Signed-off-by: Jan Michalski --- src/test/groovy/checkTestTags.groovy | 147 ++++++++++++++++++++++----- 1 file changed, 119 insertions(+), 28 deletions(-) diff --git a/src/test/groovy/checkTestTags.groovy b/src/test/groovy/checkTestTags.groovy index 45c7af543..64afa9023 100644 --- a/src/test/groovy/checkTestTags.groovy +++ b/src/test/groovy/checkTestTags.groovy @@ -6,26 +6,95 @@ package com.daos.pipeline +import static org.junit.jupiter.api.Assertions.* import spock.lang.Specification class TagTemplateSpec extends Specification { - 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'], + 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: [:] ] - def tagTemplate = 'line1,@stages.tag@ line2,@stages.tag@ ' + - 'line3,@stages.tag@ line4,@stages.tag@' - when: "we expand the template by replacing each @stages.tag@ with the next tag.value" - def result = expandTagTemplate(tagTemplate, tags) + 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' } - then: "the result matches the expected expansion" - result == 'line1,line1 line2,line2 line3,line3 line4,line4' + 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) } /** @@ -33,20 +102,42 @@ class TagTemplateSpec extends Specification { * 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(String template, List tags) { - def placeholder = '@stages.tag@' - def sb = new StringBuilder(template) - int searchFrom = 0 - tags.each { t -> - int idx = sb.indexOf(placeholder, searchFrom) - if (idx == -1) { - // no more placeholders; stop replacing - return sb.toString() - } - sb.replace(idx, idx + placeholder.length(), t.value) - // move searchFrom past the inserted value to avoid re-matching inside it - searchFrom = idx + t.value.length() - } - return sb.toString() + private String expandTagTemplate(List tags) { + String cm = '''\ + Test commit\n''' + tags.each { tag -> + cm += """\ + + ${tag.tag}: ${tag.value}""" + } + // print('Running test with commit:\n' + cm) + // assign Map to env. var to serialize it + 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() + // print(info) + 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(result, tagTemplate) } } From 807850b3901e002320041030c772b9c3fd4d9434 Mon Sep 17 00:00:00 2001 From: Jan Michalski Date: Thu, 14 May 2026 13:03:35 +0000 Subject: [PATCH 18/29] Necessary fix! Signed-off-by: Jan Michalski --- vars/getFunctionalTags.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vars/getFunctionalTags.groovy b/vars/getFunctionalTags.groovy index 38efaf128..f91b4331a 100644 --- a/vars/getFunctionalTags.groovy +++ b/vars/getFunctionalTags.groovy @@ -12,7 +12,7 @@ * 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 */ From b8984c2fbc8e9d53a1fd5684963817be4bbe9fb4 Mon Sep 17 00:00:00 2001 From: Jan Michalski Date: Thu, 14 May 2026 14:05:24 +0000 Subject: [PATCH 19/29] fix assert order Signed-off-by: Jan Michalski --- src/test/groovy/checkTestTags.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/groovy/checkTestTags.groovy b/src/test/groovy/checkTestTags.groovy index 64afa9023..958380d56 100644 --- a/src/test/groovy/checkTestTags.groovy +++ b/src/test/groovy/checkTestTags.groovy @@ -138,6 +138,6 @@ class TagTemplateSpec extends Specification { def tagTemplate = 'line1,vm line2,vm line3,vm line4,vm' def result = expandTagTemplate(tags) - assertEquals(result, tagTemplate) + assertEquals(tagTemplate, result) } } From 675dcfe8108e67d2e62e01110d06ef3bd3a3613c Mon Sep 17 00:00:00 2001 From: Jan Michalski Date: Thu, 14 May 2026 14:05:46 +0000 Subject: [PATCH 20/29] Add debug prints Signed-off-by: Jan Michalski --- src/test/groovy/checkTestTags.groovy | 2 ++ vars/commitPragma.groovy | 10 ++++++++++ vars/envToPragmas.groovy | 1 + 3 files changed, 13 insertions(+) diff --git a/src/test/groovy/checkTestTags.groovy b/src/test/groovy/checkTestTags.groovy index 958380d56..56b30d7ff 100644 --- a/src/test/groovy/checkTestTags.groovy +++ b/src/test/groovy/checkTestTags.groovy @@ -114,6 +114,8 @@ class TagTemplateSpec extends Specification { // assign Map to env. var to serialize it Script pragmasToEnv = loadPragmasToEnv() String tmp_pragmas = pragmasToEnv.call(cm.stripIndent()) + println('tmp_pragmas == "' + tmp_pragmas + '"') + println('tmp_pragmas.type == ' + tmp_pragmas.getClass().getName()) Map env = [ STAGE_NAME: stage_name, UNIT_TEST: 'true', diff --git a/vars/commitPragma.groovy b/vars/commitPragma.groovy index 08691520a..750b6dccc 100644 --- a/vars/commitPragma.groovy +++ b/vars/commitPragma.groovy @@ -27,16 +27,26 @@ String call(Map config = [:]) { String call(String name, String def_val = null) { if (env.pragmas) { Map pragmas = envToPragmas() + println('pragmas.type == ' + pragmas.getClass()) + println('envToPragmas output:') + pragmas.each { k, v -> + println(" '${k}': '${v}'") + } + String key = name.toLowerCase() def value = pragmas[key] if (key == 'test-tag') { + println('value.type == ' + value.getClass()) if (value instanceof List) { + println('exit A') return value.join(' ') } if (value != null) { + println('exit B') return value.toString() } + println('exit C') return def_val ?: '' } diff --git a/vars/envToPragmas.groovy b/vars/envToPragmas.groovy index 82a7988a1..da737b2b8 100644 --- a/vars/envToPragmas.groovy +++ b/vars/envToPragmas.groovy @@ -17,6 +17,7 @@ Map call() { } Map pragmas = [:] + // culprit? pragmas = "${env.pragmas}"[1..-2].split(', ').collectEntries { entry -> String[] pair = entry.split('=', 2) [(pair.first()): pair.last()] From f84767f8c6a3d3737ed103d70aac0ea7d50cd2fa Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Fri, 15 May 2026 09:38:48 +0200 Subject: [PATCH 21/29] fix envToPragmas file Signed-off-by: Oksana Salyk --- src/test/groovy/checkTestTags.groovy | 5 -- vars/commitPragma.groovy | 9 --- vars/envToPragmas.groovy | 102 +++++++++++++++++++++++++-- 3 files changed, 95 insertions(+), 21 deletions(-) diff --git a/src/test/groovy/checkTestTags.groovy b/src/test/groovy/checkTestTags.groovy index 56b30d7ff..3f74fcb01 100644 --- a/src/test/groovy/checkTestTags.groovy +++ b/src/test/groovy/checkTestTags.groovy @@ -110,12 +110,8 @@ class TagTemplateSpec extends Specification { ${tag.tag}: ${tag.value}""" } - // print('Running test with commit:\n' + cm) - // assign Map to env. var to serialize it Script pragmasToEnv = loadPragmasToEnv() String tmp_pragmas = pragmasToEnv.call(cm.stripIndent()) - println('tmp_pragmas == "' + tmp_pragmas + '"') - println('tmp_pragmas.type == ' + tmp_pragmas.getClass().getName()) Map env = [ STAGE_NAME: stage_name, UNIT_TEST: 'true', @@ -125,7 +121,6 @@ class TagTemplateSpec extends Specification { ] Script parseStageInfo = loadParseStageInfo([env: env]) Map info = parseStageInfo.call() - // print(info) return info['test_tag'] } diff --git a/vars/commitPragma.groovy b/vars/commitPragma.groovy index 750b6dccc..96e4752d5 100644 --- a/vars/commitPragma.groovy +++ b/vars/commitPragma.groovy @@ -27,26 +27,17 @@ String call(Map config = [:]) { String call(String name, String def_val = null) { if (env.pragmas) { Map pragmas = envToPragmas() - println('pragmas.type == ' + pragmas.getClass()) - println('envToPragmas output:') - pragmas.each { k, v -> - println(" '${k}': '${v}'") - } String key = name.toLowerCase() def value = pragmas[key] if (key == 'test-tag') { - println('value.type == ' + value.getClass()) if (value instanceof List) { - println('exit A') return value.join(' ') } if (value != null) { - println('exit B') return value.toString() } - println('exit C') return def_val ?: '' } diff --git a/vars/envToPragmas.groovy b/vars/envToPragmas.groovy index da737b2b8..06a3a42e7 100644 --- a/vars/envToPragmas.groovy +++ b/vars/envToPragmas.groovy @@ -12,16 +12,104 @@ Map call() { return [:] } + // If already a Map, return as-is (normalize keys to lowercase) if (env.pragmas instanceof Map) { - return (Map) env.pragmas + Map m = [:] + ((Map) env.pragmas).each { k, v -> + m[k.toString().toLowerCase()] = v + } + // ensure test-tag is a list if present + if (m['test-tag'] != null && !(m['test-tag'] instanceof List)) { + m['test-tag'] = (m['test-tag'] instanceof String) ? [m['test-tag']] : [m['test-tag'].toString()] + } + return m } - Map pragmas = [:] - // culprit? - pragmas = "${env.pragmas}"[1..-2].split(', ').collectEntries { entry -> - String[] pair = entry.split('=', 2) - [(pair.first()): pair.last()] + String s = env.pragmas.toString().trim() + + // Try several common serialized forms and parse them robustly + + // 1) Form like: [test-tag:[line1, line2, line3]] + def m1 = s =~ /^\s* + +\[([^\: + +\[\] + +]+)\:\s* + +\[([^\] + +]*)\] + +\] + +\s*$/ + if (m1.matches()) { + String key = m1[0][1].trim().toLowerCase() + String inner = m1[0][2].trim() + List values = inner ? inner.split(/\s*,\s*/).collect { it.trim() } : [] + return [(key): values] } - return pragmas + // 2) Form like: {test-tag=[line1, line2], other=val} + // or {test-tag:[line1, line2], other:val} + // Normalize braces and separators, then parse top-level entries + try { + String body = s + if (body.startsWith('{') && body.endsWith('}')) { + body = body[1..-2].trim() + } else if (body.startsWith('[') && body.endsWith(']')) { + // already handled the nested-list case above; fall back to inner content + body = body[1..-2].trim() + } + + Map pragmas = [:] + // split top-level entries on comma that are not inside brackets + 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 + // accept both key=value, key: value, key=[...], key:[...] + def kv = entry.split(/[:=]/, 2) + if (kv.length == 0) return + String key = kv[0].trim().toLowerCase() + String rawVal = (kv.length > 1) ? kv[1].trim() : '' + // strip surrounding braces/brackets + if (rawVal.startsWith('[') && rawVal.endsWith(']')) { + String inner = rawVal[1..-2].trim() + List vals = inner ? inner.split(/\s*,\s*/).collect { it.trim() } : [] + pragmas[key] = vals + } else if (rawVal.startsWith('{') && rawVal.endsWith('}')) { + // nested map — keep as string representation + pragmas[key] = rawVal + } else { + // plain scalar + pragmas[key] = rawVal + } + } + + // Ensure test-tag is a list + if (pragmas['test-tag'] != null && !(pragmas['test-tag'] instanceof List)) { + pragmas['test-tag'] = [pragmas['test-tag'].toString()] + } + + return pragmas + } catch (Exception e) { + // fallback: return empty map to avoid NPEs downstream + return [:] + } } From 411d49506001af72c55b2fff6505c24f0e382ada Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Fri, 15 May 2026 09:45:42 +0200 Subject: [PATCH 22/29] Use your branch Signed-off-by: Oksana Salyk --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index e905232a5..cd4fee3e2 100755 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -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 = [:] From 81bc236f7a4b706c920afb4783805a3d89fa2e2f Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Fri, 15 May 2026 12:25:07 +0200 Subject: [PATCH 23/29] =?UTF-8?q?Replace=20=E2=80=9CMatcher=E2=80=9D=20wit?= =?UTF-8?q?h=20=E2=80=9CString=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oksana Salyk --- vars/envToPragmas.groovy | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vars/envToPragmas.groovy b/vars/envToPragmas.groovy index 06a3a42e7..730de48b1 100644 --- a/vars/envToPragmas.groovy +++ b/vars/envToPragmas.groovy @@ -12,6 +12,11 @@ Map call() { return [:] } + if (env.pragmas instanceof java.util.regex.Matcher) { + def m = (java.util.regex.Matcher) env.pragmas + env.pragmas = m.find() ? m.group(0) : m.toString() + } + // If already a Map, return as-is (normalize keys to lowercase) if (env.pragmas instanceof Map) { Map m = [:] From f4f79beebbcf1f7d50f9442bb8d534ab83c9b266 Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Fri, 15 May 2026 12:28:26 +0200 Subject: [PATCH 24/29] Fix gradle test Signed-off-by: Oksana Salyk --- src/test/groovy/checkTestTags.groovy | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/test/groovy/checkTestTags.groovy b/src/test/groovy/checkTestTags.groovy index 3f74fcb01..5888a47ce 100644 --- a/src/test/groovy/checkTestTags.groovy +++ b/src/test/groovy/checkTestTags.groovy @@ -7,6 +7,7 @@ package com.daos.pipeline import static org.junit.jupiter.api.Assertions.* + import spock.lang.Specification class TagTemplateSpec extends Specification { @@ -52,7 +53,8 @@ class TagTemplateSpec extends Specification { extraBinding.error = { String a ->} extraBinding.getPragmaSuffix = {-> return 'XXD' } - Closure getFunctionalStageTagsWrap = { -> + Closure getFunctionalStageTagsWrap = { + -> Script getFunctionalStageTags = loadScript('getFunctionalStageTags', [ env: extraBinding.env, ]) @@ -62,13 +64,15 @@ class TagTemplateSpec extends Specification { extraBinding.startedByTimer = {-> return false } extraBinding.branchTypeRE = { String a -> return 'XXE' } - Closure getPragmaSuffixWrap = { -> + Closure getPragmaSuffixWrap = { + -> Script getPragmaSuffix = loadScript('getPragmaSuffix', [ env: extraBinding.env, ]) return getPragmaSuffix.call() } - Closure envToPragmasWrap = { -> + Closure envToPragmasWrap = { + -> Script envToPragmas = loadScript('envToPragmas', [ env: extraBinding.env, ]) @@ -89,7 +93,9 @@ class TagTemplateSpec extends Specification { startedByUpstream: {-> return false }, startedByTimer: {-> return false }, commitPragma: commitPragmaWrap, - getSkippedTests: {-> return [] } + getSkippedTests: { + -> return [] + } ]) return getFunctionalTags.call(kwargs) } From f34ad8151110e2fa34026f4022444b4165947d3e Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Fri, 15 May 2026 13:21:53 +0200 Subject: [PATCH 25/29] improve envToPragmas Signed-off-by: Oksana Salyk --- vars/envToPragmas.groovy | 66 ++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/vars/envToPragmas.groovy b/vars/envToPragmas.groovy index 730de48b1..0cc5d33bb 100644 --- a/vars/envToPragmas.groovy +++ b/vars/envToPragmas.groovy @@ -30,7 +30,10 @@ Map call() { return m } - String s = env.pragmas.toString().trim() + def pragmas = env.pragmas + String s = (pragmas instanceof String) ? pragmas.trim() : + (pragmas instanceof List) ? pragmas.collect { it?.toString()?.trim() }.join(',') : + pragmas?.toString()?.trim() // Try several common serialized forms and parse them robustly @@ -51,8 +54,10 @@ Map call() { \s*$/ if (m1.matches()) { - String key = m1[0][1].trim().toLowerCase() - String inner = m1[0][2].trim() + def rawKey = m1[0][1] + String key = (rawKey instanceof String) ? rawKey.trim().toLowerCase() : rawKey?.toString()?.trim()?.toLowerCase() + def rawInner = m1[0][2] + String inner = (rawInner instanceof String) ? rawInner.trim() : rawInner?.toString()?.trim() List values = inner ? inner.split(/\s*,\s*/).collect { it.trim() } : [] return [(key): values] } @@ -61,16 +66,16 @@ Map call() { // or {test-tag:[line1, line2], other:val} // Normalize braces and separators, then parse top-level entries try { - String body = s + String body = s ?: '' if (body.startsWith('{') && body.endsWith('}')) { - body = body[1..-2].trim() + body = (body.length() > 2) ? body[1..-2].trim() : '' } else if (body.startsWith('[') && body.endsWith(']')) { // already handled the nested-list case above; fall back to inner content - body = body[1..-2].trim() + body = (body.length() > 2) ? body[1..-2].trim() : '' } - Map pragmas = [:] - // split top-level entries on comma that are not inside brackets + Map pragmasMap = [:] + // split top-level entries on comma that are not inside brackets/braces def parts = [] int depth = 0 StringBuilder cur = new StringBuilder() @@ -91,28 +96,43 @@ Map call() { // accept both key=value, key: value, key=[...], key:[...] def kv = entry.split(/[:=]/, 2) if (kv.length == 0) return - String key = kv[0].trim().toLowerCase() - String rawVal = (kv.length > 1) ? kv[1].trim() : '' - // strip surrounding braces/brackets - if (rawVal.startsWith('[') && rawVal.endsWith(']')) { - String inner = rawVal[1..-2].trim() - List vals = inner ? inner.split(/\s*,\s*/).collect { it.trim() } : [] - pragmas[key] = vals - } else if (rawVal.startsWith('{') && rawVal.endsWith('}')) { - // nested map — keep as string representation - pragmas[key] = rawVal + String key = kv[0]?.toString()?.trim()?.toLowerCase() + String rawVal = (kv.length > 1) ? kv[1]?.toString()?.trim() : '' + + if (rawVal) { + String rv = rawVal + // strip surrounding whitespace + rv = rv.trim() + if (rv.startsWith('[') && rv.endsWith(']')) { + String inner = (rv.length() > 2) ? rv[1..-2].trim() : '' + List values = [] + if (inner) { + def arr = inner.split(/\s*,\s*/) as List + values = arr.collect { it?.toString()?.trim() } + } + pragmasMap[key] = values + } else if (rv.startsWith('{') && rv.endsWith('}')) { + // nested map — keep as string representation (trimmed) + pragmasMap[key] = rv + } else { + // plain scalar (strip optional surrounding quotes) + String scalar = rv + if ((scalar.startsWith('"') && scalar.endsWith('"')) || (scalar.startsWith("'") && scalar.endsWith("'"))) { + scalar = scalar[1..-2] + } + pragmasMap[key] = scalar + } } else { - // plain scalar - pragmas[key] = rawVal + pragmasMap[key] = '' } } // Ensure test-tag is a list - if (pragmas['test-tag'] != null && !(pragmas['test-tag'] instanceof List)) { - pragmas['test-tag'] = [pragmas['test-tag'].toString()] + if (pragmasMap['test-tag'] != null && !(pragmasMap['test-tag'] instanceof List)) { + pragmasMap['test-tag'] = [pragmasMap['test-tag'].toString()] } - return pragmas + return pragmasMap } catch (Exception e) { // fallback: return empty map to avoid NPEs downstream return [:] From adafff99004196fc462362825392a6b3ff81a682 Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Tue, 19 May 2026 07:58:50 +0200 Subject: [PATCH 26/29] Restore the use of the master branch Signed-off-by: Oksana Salyk --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index cd4fee3e2..e905232a5 100755 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -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@osalyk/SRE-322_new-fix') _ +//@Library(value='pipeline-lib@my_branch_name') _ /* groovylint-disable-next-line CompileStatic */ job_status_internal = [:] From 16b5120d3d4063114a75199f266fcf607a2661db Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Tue, 19 May 2026 11:27:50 +0200 Subject: [PATCH 27/29] edit envToPragmas.groovy Signed-off-by: Oksana Salyk --- Jenkinsfile | 2 +- vars/envToPragmas.groovy | 3 ++- vars/parseStageInfo.groovy | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index e905232a5..cd4fee3e2 100755 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -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 = [:] diff --git a/vars/envToPragmas.groovy b/vars/envToPragmas.groovy index 0cc5d33bb..732ab6027 100644 --- a/vars/envToPragmas.groovy +++ b/vars/envToPragmas.groovy @@ -14,7 +14,8 @@ Map call() { if (env.pragmas instanceof java.util.regex.Matcher) { def m = (java.util.regex.Matcher) env.pragmas - env.pragmas = m.find() ? m.group(0) : m.toString() + def pragmasString = m.find() ? m.group(0) : m.toString() + env.pragmas = pragmasString } // If already a Map, return as-is (normalize keys to lowercase) diff --git a/vars/parseStageInfo.groovy b/vars/parseStageInfo.groovy index fd31537d1..959bab120 100755 --- a/vars/parseStageInfo.groovy +++ b/vars/parseStageInfo.groovy @@ -250,7 +250,6 @@ Map call(Map config = [:]) { kwargs['pragma_suffix'] = result['pragma_suffix'] kwargs['stage_tags'] = getFunctionalStageTags() - // przed użyciem kwargs def dt = config['test_tag'] if (!dt && config['tags'] instanceof List) { From 85ce979e9e8da2e8f14bab1ba434f2d653cbe4e6 Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Tue, 19 May 2026 12:49:25 +0200 Subject: [PATCH 28/29] Work on a local copy in envToPragmas Signed-off-by: Oksana Salyk --- vars/envToPragmas.groovy | 100 +++++++++++++++------------------------ vars/safeUtils.groovy | 34 +++++++++++++ 2 files changed, 73 insertions(+), 61 deletions(-) create mode 100644 vars/safeUtils.groovy diff --git a/vars/envToPragmas.groovy b/vars/envToPragmas.groovy index 732ab6027..abed5360e 100644 --- a/vars/envToPragmas.groovy +++ b/vars/envToPragmas.groovy @@ -8,75 +8,65 @@ */ Map call() { + // Work on a local copy to avoid storing non-serializable objects in env if (!env.pragmas) { return [:] } - if (env.pragmas instanceof java.util.regex.Matcher) { - def m = (java.util.regex.Matcher) env.pragmas - def pragmasString = m.find() ? m.group(0) : m.toString() - env.pragmas = pragmasString + 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, return as-is (normalize keys to lowercase) - if (env.pragmas instanceof Map) { + // If already a Map, normalize keys and ensure list types for test-tag + if (raw instanceof Map) { Map m = [:] - ((Map) env.pragmas).each { k, v -> + ((Map) raw).each { k, v -> m[k.toString().toLowerCase()] = v } - // ensure test-tag is a list if present if (m['test-tag'] != null && !(m['test-tag'] instanceof List)) { - m['test-tag'] = (m['test-tag'] instanceof String) ? [m['test-tag']] : [m['test-tag'].toString()] + m['test-tag'] = safeUtils.ensureList(m['test-tag']) } return m } - def pragmas = env.pragmas - String s = (pragmas instanceof String) ? pragmas.trim() : - (pragmas instanceof List) ? pragmas.collect { it?.toString()?.trim() }.join(',') : - pragmas?.toString()?.trim() - - // Try several common serialized forms and parse them robustly + // 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() - // 1) Form like: [test-tag:[line1, line2, line3]] - def m1 = s =~ /^\s* + if (!s) { + return [:] + } + try { + // 1) Form like: [test-tag:[line1, line2, line3]] + def m1 = (s =~ /^\s* \[([^\: - \[\] - -]+)\:\s* - +]+)\s*:\s* \[([^\] - ]*)\] +\s*\] +$/) + + if (m1.matches()) { + def rawKey = m1[0][1] + String key = rawKey.toString().trim().toLowerCase() + String inner = m1[0][2].toString().trim() + List values = inner ? inner.split(/\s*,\s*/).collect { it.trim() } : [] + return [(key): values] + } -\] - -\s*$/ - if (m1.matches()) { - def rawKey = m1[0][1] - String key = (rawKey instanceof String) ? rawKey.trim().toLowerCase() : rawKey?.toString()?.trim()?.toLowerCase() - def rawInner = m1[0][2] - String inner = (rawInner instanceof String) ? rawInner.trim() : rawInner?.toString()?.trim() - List values = inner ? inner.split(/\s*,\s*/).collect { it.trim() } : [] - return [(key): values] - } - - // 2) Form like: {test-tag=[line1, line2], other=val} - // or {test-tag:[line1, line2], other:val} - // Normalize braces and separators, then parse top-level entries - try { - String body = s ?: '' - if (body.startsWith('{') && body.endsWith('}')) { - body = (body.length() > 2) ? body[1..-2].trim() : '' - } else if (body.startsWith('[') && body.endsWith(']')) { - // already handled the nested-list case above; fall back to inner content + // 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 = [:] - // split top-level entries on comma that are not inside brackets/braces def parts = [] int depth = 0 StringBuilder cur = new StringBuilder() @@ -94,34 +84,23 @@ Map call() { parts.each { entry -> if (!entry) return - // accept both key=value, key: value, key=[...], key:[...] def kv = entry.split(/[:=]/, 2) if (kv.length == 0) return String key = kv[0]?.toString()?.trim()?.toLowerCase() String rawVal = (kv.length > 1) ? kv[1]?.toString()?.trim() : '' if (rawVal) { - String rv = rawVal - // strip surrounding whitespace - rv = rv.trim() + String rv = rawVal.trim() if (rv.startsWith('[') && rv.endsWith(']')) { String inner = (rv.length() > 2) ? rv[1..-2].trim() : '' - List values = [] - if (inner) { - def arr = inner.split(/\s*,\s*/) as List - values = arr.collect { it?.toString()?.trim() } - } - pragmasMap[key] = values - } else if (rv.startsWith('{') && rv.endsWith('}')) { - // nested map — keep as string representation (trimmed) - pragmasMap[key] = rv + List values = inner ? inner.split(/\s*,\s*/) as List : [] + pragmasMap[key] = values.collect { it?.toString()?.trim() } } else { - // plain scalar (strip optional surrounding quotes) String scalar = rv if ((scalar.startsWith('"') && scalar.endsWith('"')) || (scalar.startsWith("'") && scalar.endsWith("'"))) { scalar = scalar[1..-2] } - pragmasMap[key] = scalar + pragmasMap[key] = scalar.trim() } } else { pragmasMap[key] = '' @@ -130,12 +109,11 @@ Map call() { // Ensure test-tag is a list if (pragmasMap['test-tag'] != null && !(pragmasMap['test-tag'] instanceof List)) { - pragmasMap['test-tag'] = [pragmasMap['test-tag'].toString()] + pragmasMap['test-tag'] = safeUtils.ensureList(pragmasMap['test-tag']) } return pragmasMap } catch (Exception e) { - // fallback: return empty map to avoid NPEs downstream return [:] } } diff --git a/vars/safeUtils.groovy b/vars/safeUtils.groovy new file mode 100644 index 000000000..5841deb49 --- /dev/null +++ b/vars/safeUtils.groovy @@ -0,0 +1,34 @@ +/* + * Global helper available to pipeline scripts (placed in vars so no import needed). + * Exposes ensureList and safeTrim to normalize values safely. + */ + +def call() { return this } + +/** + * Ensure the value is returned as a List. + */ +def ensureList(def v) { + if (v == null) return [] + if (v instanceof List) { + return v.collect { it == null ? '' : it.toString().trim() } + } + if (v instanceof String) { + String s = v.trim() + if (s == '') return [] + if (s.contains(',')) { + return s.split(/\s*,\s*/).collect { it.trim() } + } + return [s] + } + return [v.toString().trim()] +} + +/** + * Return a trimmed String if input is String-like, otherwise null. + */ +def safeTrim(def v) { + if (v == null) return null + if (v instanceof String) return v.trim() + return v.toString().trim() +} From a09fc166151357951c3d08d6e63cb7193b37c3fa Mon Sep 17 00:00:00 2001 From: Oksana Salyk Date: Tue, 19 May 2026 14:46:49 +0200 Subject: [PATCH 29/29] edit envToPragmas file Signed-off-by: Oksana Salyk --- vars/envToPragmas.groovy | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/vars/envToPragmas.groovy b/vars/envToPragmas.groovy index abed5360e..367d4610f 100644 --- a/vars/envToPragmas.groovy +++ b/vars/envToPragmas.groovy @@ -54,9 +54,10 @@ $/) if (m1.matches()) { def rawKey = m1[0][1] - String key = rawKey.toString().trim().toLowerCase() - String inner = m1[0][2].toString().trim() - List values = inner ? inner.split(/\s*,\s*/).collect { it.trim() } : [] + 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] } @@ -86,15 +87,18 @@ $/) 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]?.toString()?.trim() : '' + String key = kv[0]?.toString()?.trim()?.toLowerCase() ?: '' + String rawVal = (kv.length > 1) ? kv[1] : '' - if (rawVal) { - String rv = rawVal.trim() + 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*/) as List : [] - pragmasMap[key] = values.collect { it?.toString()?.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("'"))) { @@ -102,8 +106,6 @@ $/) } pragmasMap[key] = scalar.trim() } - } else { - pragmasMap[key] = '' } }