Skip to content

Commit

Permalink
Fix GradleConsoleAnnotator when Timestamper is globally enabled
Browse files Browse the repository at this point in the history
Fixes JENKINS-72411.
  • Loading branch information
alextu committed Dec 8, 2023
1 parent bf07e59 commit e2156be
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 19 deletions.
12 changes: 11 additions & 1 deletion src/main/java/hudson/plugins/gradle/GradleConsoleAnnotator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="https://github.com/jenkinsci/ant-plugin/blob/master/src/main/java/hudson/tasks/_ant/AntConsoleAnnotator.java">AntConsoleAnnotator</a>
Expand All @@ -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,
Expand All @@ -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);
}

Expand Down
9 changes: 6 additions & 3 deletions src/main/java/hudson/plugins/gradle/GradleOutcomeNote.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,16 @@

import java.util.regex.Pattern;

import static hudson.plugins.gradle.TimestampPrefixDetector.TimestampPattern;

/**
* Annotates the BUILD SUCCESSFUL/FAILED line of the Ant execution.
*
* @author ikikko
*/
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() {
}
Expand All @@ -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(),
"<span class=gradle-outcome-failure>", "</span>");
if (text.getText().contains("SUCCESS"))
text.addMarkup(0, buildStatus.length(),
text.addMarkup(timestampPrefix, timestampPrefix + buildStatus.length(),
"<span class=gradle-outcome-success>", "</span>");
return null;
}
Expand Down
17 changes: 10 additions & 7 deletions src/main/java/hudson/plugins/gradle/GradleTaskNote.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> progressStatuses = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
Expand All @@ -21,15 +23,16 @@ 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) {
// still under development. too early to put into production
if (!ENABLED)
return null;

int timestampPrefix = TimestampPrefixDetector.detectTimestampPrefix(text.getText());
int prefixLength = 1;
MarkupText.SubText t = text.findToken(TASK_PATTERN_1);
if (t == null) {
Expand All @@ -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, "<b class=gradle-task>", "</b>");
}
if (progressStatus != null && !progressStatus.isEmpty()
t.addMarkup(timestampPrefix + 1, timestampPrefix + task.length() + prefixLength, "<b class=\"gradle-task\">", "</b>");
if (progressStatus != null && !progressStatus.isEmpty()
&& progressStatuses.contains(progressStatus)) {
t.addMarkup(task.length() + delimiterSpace.length() + prefixLength,
text.length(), "<span class=gradle-task-progress-status>",
t.addMarkup(timestampPrefix + task.length() + delimiterSpace.length() + prefixLength,
text.length(), "<span class=\"gradle-task-progress-status\">",
"</span>");
}
}

return null;
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/hudson/plugins/gradle/TimestampPrefixDetector.java
Original file line number Diff line number Diff line change
@@ -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;
}


}
Original file line number Diff line number Diff line change
@@ -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 ]
}

}
16 changes: 8 additions & 8 deletions src/test/groovy/hudson/plugins/gradle/GradleTaskNoteTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ class GradleTaskNoteTest extends Specification {

where:
consoleOutput | annotatedOutput
':TASK' | ':<b class=gradle-task>TASK</b>'
':TASK UP-TO-DATE' | ':<b class=gradle-task>TASK</b> <span class=gradle-task-progress-status>UP-TO-DATE</span>'
':TASK SKIPPED' | ':<b class=gradle-task>TASK</b> <span class=gradle-task-progress-status>SKIPPED</span>'
':TASK FROM-CACHE' | ':<b class=gradle-task>TASK</b> <span class=gradle-task-progress-status>FROM-CACHE</span>'
':TASK NO-SOURCE' | ':<b class=gradle-task>TASK</b> <span class=gradle-task-progress-status>NO-SOURCE</span>'
':TASK DUMMY' | ':<b class=gradle-task>TASK</b> DUMMY'
':TASK' | ':<b class="gradle-task">TASK</b>'
':TASK UP-TO-DATE' | ':<b class="gradle-task">TASK</b> <span class="gradle-task-progress-status">UP-TO-DATE</span>'
':TASK SKIPPED' | ':<b class="gradle-task">TASK</b> <span class="gradle-task-progress-status">SKIPPED</span>'
':TASK FROM-CACHE' | ':<b class="gradle-task">TASK</b> <span class="gradle-task-progress-status">FROM-CACHE</span>'
':TASK NO-SOURCE' | ':<b class="gradle-task">TASK</b> <span class="gradle-task-progress-status">NO-SOURCE</span>'
':TASK DUMMY' | ':<b class="gradle-task">TASK</b> DUMMY'
':::: ERRORS' | ':::: ERRORS'
':PARENT:TASK' | ':<b class=gradle-task>PARENT:TASK</b>'
':PARENT:TASK UP-TO-DATE' | ':<b class=gradle-task>PARENT:TASK</b> <span class=gradle-task-progress-status>UP-TO-DATE</span>'
':PARENT:TASK' | ':<b class="gradle-task">PARENT:TASK</b>'
':PARENT:TASK UP-TO-DATE' | ':<b class="gradle-task">PARENT:TASK</b> <span class="gradle-task-progress-status">UP-TO-DATE</span>'
}

void 'no annotation when disabled'() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
}

}

0 comments on commit e2156be

Please sign in to comment.