From e2156be7edfd691194d5c587ea221bd03a66fdee Mon Sep 17 00:00:00 2001 From: Alexis Tual Date: Fri, 8 Dec 2023 18:50:14 +0100 Subject: [PATCH 1/5] Fix GradleConsoleAnnotator when Timestamper is globally enabled Fixes JENKINS-72411. --- .../gradle/GradleConsoleAnnotator.java | 12 +++- .../plugins/gradle/GradleOutcomeNote.java | 9 ++- .../hudson/plugins/gradle/GradleTaskNote.java | 17 +++-- .../gradle/TimestampPrefixDetector.java | 24 +++++++ ...adleConsoleAnnotatorIntegrationTest.groovy | 72 +++++++++++++++++++ .../plugins/gradle/GradleTaskNoteTest.groovy | 16 ++--- .../gradle/TimestampPrefixDetectorTest.groovy | 36 ++++++++++ 7 files changed, 167 insertions(+), 19 deletions(-) create mode 100644 src/main/java/hudson/plugins/gradle/TimestampPrefixDetector.java create mode 100644 src/test/groovy/hudson/plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy create mode 100644 src/test/groovy/hudson/plugins/gradle/TimestampPrefixDetectorTest.groovy diff --git a/src/main/java/hudson/plugins/gradle/GradleConsoleAnnotator.java b/src/main/java/hudson/plugins/gradle/GradleConsoleAnnotator.java index 3a37fd89..743c4fbc 100644 --- a/src/main/java/hudson/plugins/gradle/GradleConsoleAnnotator.java +++ b/src/main/java/hudson/plugins/gradle/GradleConsoleAnnotator.java @@ -4,6 +4,9 @@ import java.io.OutputStream; import java.nio.charset.Charset; +import static hudson.plugins.gradle.TimestampPrefixDetector.detectTimestampPrefix; +import static hudson.plugins.gradle.TimestampPrefixDetector.trimTimestampPrefix; + /** * @author ikikko * @see AntConsoleAnnotator @@ -13,6 +16,8 @@ public final class GradleConsoleAnnotator extends AbstractGradleLogProcessor { private final boolean annotateGradleOutput; private final BuildScanLogScanner buildScanLogScanner; + private transient Integer timestampPrefix; + public GradleConsoleAnnotator(OutputStream out, Charset charset, boolean annotateGradleOutput, @@ -28,7 +33,12 @@ protected void processLogLine(String line) throws IOException { line = trimEOL(line); if (annotateGradleOutput) { - if (line.startsWith(":") || line.startsWith("> Task :")) { // put the annotation + // assumption that the timestamp prefix will be present or not for all lines + if (timestampPrefix == null) { + timestampPrefix = detectTimestampPrefix(line); + } + line = trimTimestampPrefix(timestampPrefix, line); + if (line.startsWith(":") || line.startsWith("> Task :")) { new GradleTaskNote().encodeTo(out); } diff --git a/src/main/java/hudson/plugins/gradle/GradleOutcomeNote.java b/src/main/java/hudson/plugins/gradle/GradleOutcomeNote.java index 133873c8..f0b1a6f0 100644 --- a/src/main/java/hudson/plugins/gradle/GradleOutcomeNote.java +++ b/src/main/java/hudson/plugins/gradle/GradleOutcomeNote.java @@ -31,6 +31,8 @@ import java.util.regex.Pattern; +import static hudson.plugins.gradle.TimestampPrefixDetector.TimestampPattern; + /** * Annotates the BUILD SUCCESSFUL/FAILED line of the Ant execution. * @@ -38,7 +40,7 @@ */ public class GradleOutcomeNote extends ConsoleNote { - private static final Pattern BUILD_RESULT_PATTERN = Pattern.compile("^(BUILD \\S*)"); + private static final Pattern BUILD_RESULT_PATTERN = Pattern.compile("^(?:" + TimestampPattern + ")?(BUILD \\S*)"); public GradleOutcomeNote() { } @@ -49,12 +51,13 @@ public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) { if (t == null) { return null; } + int timestampPrefix = TimestampPrefixDetector.detectTimestampPrefix(text.getText()); String buildStatus = t.group(1); if (text.getText().contains("FAIL")) - text.addMarkup(0, buildStatus.length(), + text.addMarkup(timestampPrefix, timestampPrefix + buildStatus.length(), "", ""); if (text.getText().contains("SUCCESS")) - text.addMarkup(0, buildStatus.length(), + text.addMarkup(timestampPrefix, timestampPrefix + buildStatus.length(), "", ""); return null; } diff --git a/src/main/java/hudson/plugins/gradle/GradleTaskNote.java b/src/main/java/hudson/plugins/gradle/GradleTaskNote.java index 14bbd707..0a3c6320 100644 --- a/src/main/java/hudson/plugins/gradle/GradleTaskNote.java +++ b/src/main/java/hudson/plugins/gradle/GradleTaskNote.java @@ -12,6 +12,8 @@ import java.util.HashSet; import java.util.regex.Pattern; +import static hudson.plugins.gradle.TimestampPrefixDetector.TimestampPattern; + public final class GradleTaskNote extends ConsoleNote { private static final Collection progressStatuses = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( @@ -21,8 +23,8 @@ public final class GradleTaskNote extends ConsoleNote { "NO-SOURCE" ))); - private static final Pattern TASK_PATTERN_1 = Pattern.compile("^:([^:]\\S*)(\\s*)(\\S*)"); - private static final Pattern TASK_PATTERN_2 = Pattern.compile("^> Task :([^:]\\S*)(\\s*)(\\S*)"); + private static final Pattern TASK_PATTERN_1 = Pattern.compile("^(?:" + TimestampPattern + ")?:([^:]\\S*)(\\s*)(\\S*)"); + private static final Pattern TASK_PATTERN_2 = Pattern.compile("^(?:" + TimestampPattern + ")?> Task :([^:]\\S*)(\\s*)(\\S*)"); @Override public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) { @@ -30,6 +32,7 @@ public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) { if (!ENABLED) return null; + int timestampPrefix = TimestampPrefixDetector.detectTimestampPrefix(text.getText()); int prefixLength = 1; MarkupText.SubText t = text.findToken(TASK_PATTERN_1); if (t == null) { @@ -46,13 +49,13 @@ public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) { // annotate task and progress status if (task != null && !task.isEmpty()) { - t.addMarkup(1, task.length() + prefixLength, "", ""); - } - if (progressStatus != null && !progressStatus.isEmpty() + t.addMarkup(timestampPrefix + 1, timestampPrefix + task.length() + prefixLength, "", ""); + if (progressStatus != null && !progressStatus.isEmpty() && progressStatuses.contains(progressStatus)) { - t.addMarkup(task.length() + delimiterSpace.length() + prefixLength, - text.length(), "", + t.addMarkup(timestampPrefix + task.length() + delimiterSpace.length() + prefixLength, + text.length(), "", ""); + } } return null; diff --git a/src/main/java/hudson/plugins/gradle/TimestampPrefixDetector.java b/src/main/java/hudson/plugins/gradle/TimestampPrefixDetector.java new file mode 100644 index 00000000..0463ede7 --- /dev/null +++ b/src/main/java/hudson/plugins/gradle/TimestampPrefixDetector.java @@ -0,0 +1,24 @@ +package hudson.plugins.gradle; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +class TimestampPrefixDetector { + + static final String TimestampPattern = "\\[\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z\\] "; + private static final Pattern TimestampPatternR = Pattern.compile("^(" + TimestampPattern + ").*\n?$"); + + static String trimTimestampPrefix(int prefix, String line) { + return line.substring(prefix); + } + + static int detectTimestampPrefix(String line) { + Matcher matcher = TimestampPatternR.matcher(line); + if (matcher.matches()) { + return matcher.group(1).length(); + } + return 0; + } + + +} diff --git a/src/test/groovy/hudson/plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy b/src/test/groovy/hudson/plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy new file mode 100644 index 00000000..76703b2c --- /dev/null +++ b/src/test/groovy/hudson/plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy @@ -0,0 +1,72 @@ +package hudson.plugins.gradle + +import hudson.plugins.timestamper.TimestamperConfig +import hudson.slaves.DumbSlave +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition +import org.jenkinsci.plugins.workflow.job.WorkflowJob + +class GradleConsoleAnnotatorIntegrationTest extends BaseGradleIntegrationTest { + + def 'pipeline job has Gradle tasks annotated'(boolean enableTimestamper) { + given: + def gradleVersion = '8.1.1' + gradleInstallationRule.gradleVersion = gradleVersion + gradleInstallationRule.addInstallation() + + DumbSlave agent = createSlave('foo') + def pipelineJob = j.createProject(WorkflowJob) + + pipelineJob.setDefinition(new CpsFlowDefinition(""" + stage('Build') { + node('foo') { + withGradle { + git branch: 'main', url: 'https://github.com/c00ler/simple-gradle-project' + def gradleHome = tool name: '${gradleInstallationRule.gradleVersion}', type: 'gradle' + if (isUnix()) { + sh "'\${gradleHome}/bin/gradle' clean build --no-daemon --console=plain" + } else { + bat(/"\${gradleHome}\\bin\\gradle.bat" clean build --no-daemon --console=plain/) + } + } + } + } +""", false)) + + if (enableTimestamper) { + TimestamperConfig config = TimestamperConfig.get() + config.setAllPipelines(true) + config.save() + } + + when: + def b = j.buildAndAssertSuccess(pipelineJob) + + then: + def client = j.createWebClient() + def html = client.goTo(b.getUrl() + "console") + html.getByXPath("//b[@class='gradle-task']")*.textContent*.toString() == [ + ' Task :clean', + ' Task :compileJava', + ' Task :processResources', + ' Task :classes', + ' Task :jar', + ' Task :assemble', + ' Task :compileTestJava', + ' Task :processTestResources', + ' Task :testClasses', + ' Task :test', + ' Task :check', + ' Task :build' + ] + html.getByXPath("//span[@class='gradle-task-progress-status']")*.textContent*.toString() == [ + 'UP-TO-DATE\n', + 'NO-SOURCE\n', + 'NO-SOURCE\n' + ] + html.getByXPath("//span[@class='gradle-outcome-success']")*.textContent*.toString() == ['BUILD SUCCESSFUL'] + + where: + enableTimestamper << [ true, false ] + } + +} diff --git a/src/test/groovy/hudson/plugins/gradle/GradleTaskNoteTest.groovy b/src/test/groovy/hudson/plugins/gradle/GradleTaskNoteTest.groovy index 1a5c9836..1d1e9953 100644 --- a/src/test/groovy/hudson/plugins/gradle/GradleTaskNoteTest.groovy +++ b/src/test/groovy/hudson/plugins/gradle/GradleTaskNoteTest.groovy @@ -17,15 +17,15 @@ class GradleTaskNoteTest extends Specification { where: consoleOutput | annotatedOutput - ':TASK' | ':TASK' - ':TASK UP-TO-DATE' | ':TASK UP-TO-DATE' - ':TASK SKIPPED' | ':TASK SKIPPED' - ':TASK FROM-CACHE' | ':TASK FROM-CACHE' - ':TASK NO-SOURCE' | ':TASK NO-SOURCE' - ':TASK DUMMY' | ':TASK DUMMY' + ':TASK' | ':TASK' + ':TASK UP-TO-DATE' | ':TASK UP-TO-DATE' + ':TASK SKIPPED' | ':TASK SKIPPED' + ':TASK FROM-CACHE' | ':TASK FROM-CACHE' + ':TASK NO-SOURCE' | ':TASK NO-SOURCE' + ':TASK DUMMY' | ':TASK DUMMY' ':::: ERRORS' | ':::: ERRORS' - ':PARENT:TASK' | ':PARENT:TASK' - ':PARENT:TASK UP-TO-DATE' | ':PARENT:TASK UP-TO-DATE' + ':PARENT:TASK' | ':PARENT:TASK' + ':PARENT:TASK UP-TO-DATE' | ':PARENT:TASK UP-TO-DATE' } void 'no annotation when disabled'() { diff --git a/src/test/groovy/hudson/plugins/gradle/TimestampPrefixDetectorTest.groovy b/src/test/groovy/hudson/plugins/gradle/TimestampPrefixDetectorTest.groovy new file mode 100644 index 00000000..b0502e8c --- /dev/null +++ b/src/test/groovy/hudson/plugins/gradle/TimestampPrefixDetectorTest.groovy @@ -0,0 +1,36 @@ +package hudson.plugins.gradle + +import spock.lang.Specification + +class TimestampPrefixDetectorTest extends Specification { + + def "detect timestamp on line"() { + when: + def prefix = TimestampPrefixDetector.detectTimestampPrefix(line) + + then: + prefix == expected + + where: + line | expected + '[2023-12-08T10:05:56.488Z] > Task :compileJava' | 27 + '[2023-12-08T10:05:56.488Z] > Task :compileJava\n' | 27 + '[2023-12-08T10:05:56.488Z]> Task :compileJava' | 0 + '[2023-12-08T10:05:56] > Task :compileJava' | 0 + 'some message' | 0 + } + + def "trim timestamp"() { + when: + def trimmed = TimestampPrefixDetector.trimTimestampPrefix(prefix, line) + + then: + trimmed == expected + + where: + line | prefix | expected + '[2023-12-08T10:05:56.488Z] > Task :compileJava' | 27 | '> Task :compileJava' + '> Task :compileJava' | 0 | '> Task :compileJava' + } + +} From f83270d1e7cbe170a10326b5e2eb5df3d77cc171 Mon Sep 17 00:00:00 2001 From: Alexis Tual Date: Mon, 11 Dec 2023 11:50:52 +0100 Subject: [PATCH 2/5] Add logs to debug Windows tests... --- .../plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/groovy/hudson/plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy b/src/test/groovy/hudson/plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy index 76703b2c..e3fa69a0 100644 --- a/src/test/groovy/hudson/plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy +++ b/src/test/groovy/hudson/plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy @@ -4,6 +4,7 @@ import hudson.plugins.timestamper.TimestamperConfig import hudson.slaves.DumbSlave import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition import org.jenkinsci.plugins.workflow.job.WorkflowJob +import org.jvnet.hudson.test.JenkinsRule class GradleConsoleAnnotatorIntegrationTest extends BaseGradleIntegrationTest { @@ -42,6 +43,7 @@ class GradleConsoleAnnotatorIntegrationTest extends BaseGradleIntegrationTest { def b = j.buildAndAssertSuccess(pipelineJob) then: + println "logs: \n${JenkinsRule.getLog(b)}" def client = j.createWebClient() def html = client.goTo(b.getUrl() + "console") html.getByXPath("//b[@class='gradle-task']")*.textContent*.toString() == [ From 84704132b9ad937f791673e4901e498d6b620655 Mon Sep 17 00:00:00 2001 From: Alexis Tual Date: Mon, 11 Dec 2023 12:37:51 +0100 Subject: [PATCH 3/5] Fix regexp for Windows --- .../java/hudson/plugins/gradle/TimestampPrefixDetector.java | 2 +- .../plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/hudson/plugins/gradle/TimestampPrefixDetector.java b/src/main/java/hudson/plugins/gradle/TimestampPrefixDetector.java index 0463ede7..6e235775 100644 --- a/src/main/java/hudson/plugins/gradle/TimestampPrefixDetector.java +++ b/src/main/java/hudson/plugins/gradle/TimestampPrefixDetector.java @@ -6,7 +6,7 @@ class TimestampPrefixDetector { static final String TimestampPattern = "\\[\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z\\] "; - private static final Pattern TimestampPatternR = Pattern.compile("^(" + TimestampPattern + ").*\n?$"); + private static final Pattern TimestampPatternR = Pattern.compile("^(" + TimestampPattern + ").*(?:\r?\n)?$"); static String trimTimestampPrefix(int prefix, String line) { return line.substring(prefix); diff --git a/src/test/groovy/hudson/plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy b/src/test/groovy/hudson/plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy index e3fa69a0..05a78599 100644 --- a/src/test/groovy/hudson/plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy +++ b/src/test/groovy/hudson/plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy @@ -43,7 +43,6 @@ class GradleConsoleAnnotatorIntegrationTest extends BaseGradleIntegrationTest { def b = j.buildAndAssertSuccess(pipelineJob) then: - println "logs: \n${JenkinsRule.getLog(b)}" def client = j.createWebClient() def html = client.goTo(b.getUrl() + "console") html.getByXPath("//b[@class='gradle-task']")*.textContent*.toString() == [ From 0c27df8bb1b3f4d7dec11e963fd8a8f52cd49362 Mon Sep 17 00:00:00 2001 From: Alexis Tual Date: Mon, 11 Dec 2023 14:21:43 +0100 Subject: [PATCH 4/5] Remove unused import --- .../plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/groovy/hudson/plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy b/src/test/groovy/hudson/plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy index 05a78599..76703b2c 100644 --- a/src/test/groovy/hudson/plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy +++ b/src/test/groovy/hudson/plugins/gradle/GradleConsoleAnnotatorIntegrationTest.groovy @@ -4,7 +4,6 @@ import hudson.plugins.timestamper.TimestamperConfig import hudson.slaves.DumbSlave import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition import org.jenkinsci.plugins.workflow.job.WorkflowJob -import org.jvnet.hudson.test.JenkinsRule class GradleConsoleAnnotatorIntegrationTest extends BaseGradleIntegrationTest { From 4fced3088807a9401ab45be8eb839089ede96ba8 Mon Sep 17 00:00:00 2001 From: Alexis Tual Date: Wed, 13 Dec 2023 17:42:09 +0100 Subject: [PATCH 5/5] Make `TimestampPrefixDetector` final --- .../java/hudson/plugins/gradle/TimestampPrefixDetector.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/hudson/plugins/gradle/TimestampPrefixDetector.java b/src/main/java/hudson/plugins/gradle/TimestampPrefixDetector.java index 6e235775..f42aa48b 100644 --- a/src/main/java/hudson/plugins/gradle/TimestampPrefixDetector.java +++ b/src/main/java/hudson/plugins/gradle/TimestampPrefixDetector.java @@ -3,7 +3,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -class TimestampPrefixDetector { +final class TimestampPrefixDetector { static final String TimestampPattern = "\\[\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.\\d{3}Z\\] "; private static final Pattern TimestampPatternR = Pattern.compile("^(" + TimestampPattern + ").*(?:\r?\n)?$"); @@ -12,6 +12,9 @@ static String trimTimestampPrefix(int prefix, String line) { return line.substring(prefix); } + private TimestampPrefixDetector() { + } + static int detectTimestampPrefix(String line) { Matcher matcher = TimestampPatternR.matcher(line); if (matcher.matches()) { @@ -20,5 +23,4 @@ static int detectTimestampPrefix(String line) { return 0; } - }