diff --git a/core/src/main/java/hudson/model/Run.java b/core/src/main/java/hudson/model/Run.java index 52ee02bf14c4..d8ee74992094 100644 --- a/core/src/main/java/hudson/model/Run.java +++ b/core/src/main/java/hudson/model/Run.java @@ -65,6 +65,7 @@ import hudson.util.ProcessTree; import hudson.util.XStream2; +import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; @@ -1538,7 +1539,19 @@ public Collection getBuildFingerprints() { * @since 1.349 */ public void writeLogTo(long offset, @NonNull XMLOutput out) throws IOException { - getLogText().writeHtmlTo(offset, out.asWriter()); + long start = offset; + if (offset > 0) { + try (BufferedInputStream bufferedInputStream = new BufferedInputStream(getLogInputStream())) { + if (offset == bufferedInputStream.skip(offset)) { + int r; + do { + r = bufferedInputStream.read(); + start = (r == -1)? 0 : start + 1; + } while (r != -1 && r != '\n'); + } + } + } + getLogText().writeHtmlTo(start, out.asWriter()); } /** diff --git a/core/src/test/java/hudson/model/RunTest.java b/core/src/test/java/hudson/model/RunTest.java index 3248f10d86b0..ef1715a2f83a 100644 --- a/core/src/test/java/hudson/model/RunTest.java +++ b/core/src/test/java/hudson/model/RunTest.java @@ -24,9 +24,8 @@ package hudson.model; -import java.io.IOException; -import java.io.File; -import java.io.PrintWriter; +import java.io.*; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Locale; import java.util.Set; @@ -40,16 +39,21 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.console.AnnotatedLargeText; import jenkins.model.Jenkins; +import org.apache.commons.jelly.XMLOutput; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.jvnet.hudson.test.Issue; import org.jvnet.localizer.LocaleProvider; +import org.kohsuke.stapler.framework.io.ByteBuffer; import org.mockito.Mockito; public class RunTest { + private static final String SAMPLE_BUILD_OUTPUT = "Sample build output abc123.\n"; @Rule public TemporaryFolder tmp = new TemporaryFolder(); @@ -95,7 +99,7 @@ public String call() throws Exception { TimeZone.setDefault(origTZ); } } - + private List.Artifact> createArtifactList(String... paths) throws Exception { Run r = new Run(new StubJob(), 0) {}; @@ -143,7 +147,7 @@ public Locale get() { return Locale.ENGLISH; } }); - + Run r = new Run(new StubJob(), 0) {}; assertEquals("Not started yet", r.getDurationString()); r.onStartBuilding(); @@ -262,4 +266,50 @@ public void compareRunsFromDifferentParentsWithSameNumber() throws Exception { assertTrue(r1.compareTo(r2) != 0); assertTrue(treeSet.size() == 2); } + + @Test + public void willTriggerLogToStartWithNextFullLine() throws Exception { + assertWriteLogToEquals(new String(new char[2]).replace("\0", SAMPLE_BUILD_OUTPUT) + "Finished: SUCCESS.\n", 2 * SAMPLE_BUILD_OUTPUT.length() + 10); + } + + @Test + public void wontPushOffsetOnRenderingFromBeginning() throws Exception { + assertWriteLogToEquals(new String(new char[5]).replace("\0", SAMPLE_BUILD_OUTPUT) + "Finished: SUCCESS.\n", 0); + } + + @Test + public void willRenderNothingIfOffsetSetOnLastLine() throws Exception { + assertWriteLogToEquals("", 5 * SAMPLE_BUILD_OUTPUT.length() + 6); + } + + private void assertWriteLogToEquals(String expectedOutput, long offset) throws Exception { + try ( + ByteBuffer buf = new ByteBuffer(); + PrintStream ps = new PrintStream(buf, true); + StringWriter writer = new StringWriter() + ) { + for (int i = 0; i < 5; i++) { + ps.print(SAMPLE_BUILD_OUTPUT); + } + ps.print("Finished: SUCCESS.\n"); + + final Run, ? extends Run> r = new Run(Mockito.mock(Job.class)) { + @NonNull + @Override + public AnnotatedLargeText getLogText() { + return new AnnotatedLargeText<>(buf, StandardCharsets.UTF_8, true, null); + } + + @NonNull + @Override + public InputStream getLogInputStream() throws IOException { + return buf.newInputStream(); + } + }; + final XMLOutput xmlOutput = Mockito.mock(XMLOutput.class); + Mockito.when(xmlOutput.asWriter()).thenReturn(writer); + r.writeLogTo(offset, xmlOutput); + assertEquals(expectedOutput, writer.toString()); + } + } }