Skip to content

Commit d52b5c1

Browse files
committed
jenkins-client-217 - Making pooling fully asynchronous
Adding unit tests (WIP)
1 parent bfa7557 commit d52b5c1

File tree

4 files changed

+151
-72
lines changed

4 files changed

+151
-72
lines changed

jenkins-client/src/main/java/com/offbytwo/jenkins/Main.java

Lines changed: 0 additions & 46 deletions
This file was deleted.

jenkins-client/src/main/java/com/offbytwo/jenkins/helper/BuildConsoleStreamListener.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ public interface BuildConsoleStreamListener {
1212
*/
1313
void onData(String newLogChunk);
1414

15+
/**
16+
* Called by api when error happens
17+
* @param error
18+
*/
19+
void onError(Exception error);
20+
1521
/**
1622
* Called when streaming console output is finished
1723
*/

jenkins-client/src/main/java/com/offbytwo/jenkins/model/BuildWithDetails.java

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ public class BuildWithDetails extends Build {
4141

4242
private final Logger LOGGER = LoggerFactory.getLogger(getClass());
4343

44+
public final static String TEXT_SIZE_HEADER = "x-text-size";
45+
public final static String MORE_DATA_HEADER = "x-more-data";
46+
4447
/**
4548
* This will be returned by the API in cases where the build has never run.
4649
* For example {@link Build#BUILD_HAS_NEVER_RUN}
@@ -399,32 +402,47 @@ public String getConsoleOutputHtml() throws IOException {
399402
* @param poolingTimeout pooling timeout (seconds) used to break pooling in case build stuck
400403
*
401404
*/
402-
public void streamConsoleOutput(BuildConsoleStreamListener listener, int poolingInterval, int poolingTimeout) throws IOException, InterruptedException {
403-
int bufferOffset = 0;
405+
public void streamConsoleOutput(final BuildConsoleStreamListener listener, final int poolingInterval, final int poolingTimeout) {
404406
// Calculate start and timeout
405-
long startTime = System.currentTimeMillis();
406-
long timeoutTime = startTime + (poolingTimeout * 1000);
407-
// Pool for logs
408-
while (true) {
409-
Thread.sleep(poolingInterval);
410-
ConsoleLog consoleLog = getConsoleOutputText(bufferOffset);
411-
String logString = consoleLog.getConsoleLog();
412-
if (logString != null && !logString.isEmpty()) {
413-
listener.onData(logString);
414-
}
415-
if (consoleLog.getHasMoreData()) {
416-
bufferOffset = consoleLog.getCurrentBufferSize();
417-
} else {
418-
listener.finished();
419-
break;
420-
}
421-
long currentTime = System.currentTimeMillis();
422-
423-
if(currentTime> timeoutTime){
424-
LOGGER.warn("Pooling for build {0} for {2} timeout! Check if job stuck in jenkins", this.getDisplayName(), this.getNumber());
425-
break;
407+
final long startTime = System.currentTimeMillis();
408+
final long timeoutTime = startTime + (poolingTimeout * 1000);
409+
410+
// Pool for logs in separate thread
411+
new Thread(new Runnable() {
412+
public void run() {
413+
int bufferOffset = 0;
414+
while (true) {
415+
try {
416+
Thread.sleep(poolingInterval * 1000);
417+
} catch (InterruptedException e) {
418+
listener.onError(e);
419+
}
420+
ConsoleLog consoleLog = null;
421+
try {
422+
consoleLog = getConsoleOutputText(bufferOffset);
423+
} catch (IOException e) {
424+
listener.onError(e);
425+
}
426+
String logString = consoleLog.getConsoleLog();
427+
if (logString != null && !logString.isEmpty()) {
428+
listener.onData(logString);
429+
}
430+
if (consoleLog.getHasMoreData()) {
431+
bufferOffset = consoleLog.getCurrentBufferSize();
432+
} else {
433+
listener.finished();
434+
break;
435+
}
436+
long currentTime = System.currentTimeMillis();
437+
438+
if(currentTime> timeoutTime){
439+
LOGGER.warn("Pooling for build {0} for {2} timeout! Check if job stuck in jenkins",
440+
BuildWithDetails.this.getDisplayName(), BuildWithDetails.this.getNumber());
441+
break;
442+
}
443+
}
426444
}
427-
}
445+
}).start();
428446
}
429447

430448
/**
@@ -441,8 +459,9 @@ public ConsoleLog getConsoleOutputText(int bufferOffset) throws IOException {
441459
formData.add(new BasicNameValuePair("start", Integer.toString(bufferOffset)));
442460
String path = getUrl() + "logText/progressiveText";
443461
HttpResponse httpResponse = client.post_form_with_result(path, formData, false);
444-
Header moreDataHeader = httpResponse.getFirstHeader("x-more-data");
445-
Header textSizeHeader = httpResponse.getFirstHeader("x-text-size");
462+
463+
Header moreDataHeader = httpResponse.getFirstHeader(MORE_DATA_HEADER);
464+
Header textSizeHeader = httpResponse.getFirstHeader(TEXT_SIZE_HEADER);
446465
String response = EntityUtils.toString(httpResponse.getEntity());
447466
boolean hasMoreData = false;
448467
if (moreDataHeader != null) {
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package com.offbytwo.jenkins.model;
2+
3+
import com.offbytwo.jenkins.BaseUnitTest;
4+
import com.offbytwo.jenkins.client.JenkinsHttpClient;
5+
import com.offbytwo.jenkins.helper.BuildConsoleStreamListener;
6+
import org.apache.http.HttpResponse;
7+
import org.apache.http.NameValuePair;
8+
import org.apache.http.concurrent.BasicFuture;
9+
import org.apache.http.entity.StringEntity;
10+
import org.apache.http.message.BasicHeader;
11+
import org.junit.Before;
12+
import org.junit.Test;
13+
14+
import java.io.IOException;
15+
import java.util.concurrent.CompletableFuture;
16+
import java.util.concurrent.Future;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.assertj.core.api.Assertions.fail;
20+
import static org.mockito.BDDMockito.given;
21+
import static org.mockito.Matchers.anyBoolean;
22+
import static org.mockito.Matchers.anyListOf;
23+
import static org.mockito.Matchers.anyString;
24+
import static org.mockito.Mockito.mock;
25+
26+
/**
27+
*/
28+
public class BuildWithDetailsTest extends BaseUnitTest {
29+
30+
private JenkinsHttpClient client;
31+
private BuildWithDetails buildWithDetails;
32+
33+
@Before
34+
public void setUp() {
35+
client = mock(JenkinsHttpClient.class);
36+
buildWithDetails = givenBuild();
37+
}
38+
39+
private BuildWithDetails givenBuild() {
40+
BuildWithDetails buildWithDetails = new BuildWithDetails();
41+
buildWithDetails.setClient(client);
42+
return buildWithDetails;
43+
}
44+
45+
@Test
46+
public void getBuildLogWithBuffer() {
47+
try {
48+
HttpResponse response = mock(HttpResponse.class);
49+
String text = "test test test";
50+
int textLength = text.length();
51+
given(response.getEntity()).willReturn(new StringEntity(text));
52+
BasicHeader moreDataHeader = new BasicHeader(BuildWithDetails.MORE_DATA_HEADER, "false");
53+
BasicHeader textSizeHeader = new BasicHeader(BuildWithDetails.TEXT_SIZE_HEADER, Integer.toString(textLength));
54+
given(response.getFirstHeader(BuildWithDetails.MORE_DATA_HEADER)).willReturn(moreDataHeader);
55+
given(response.getFirstHeader(BuildWithDetails.TEXT_SIZE_HEADER)).willReturn(textSizeHeader);
56+
given(client.post_form_with_result(anyString(),anyListOf(NameValuePair.class),anyBoolean())).willReturn(response);
57+
ConsoleLog consoleOutputText = buildWithDetails.getConsoleOutputText(500);
58+
assertThat(consoleOutputText.getConsoleLog()).isEqualTo(text);
59+
assertThat(consoleOutputText.getCurrentBufferSize()).isEqualTo(textLength);
60+
assertThat(consoleOutputText.getHasMoreData()).isFalse();
61+
} catch (IOException e) {
62+
fail("Should not return exception",e);
63+
}
64+
}
65+
66+
67+
@Test
68+
public void poolBuildLog() throws InterruptedException {
69+
try {
70+
HttpResponse response = mock(HttpResponse.class);
71+
final String text = "test test test";
72+
int textLength = text.length();
73+
given(response.getEntity()).willReturn(new StringEntity(text));
74+
BasicHeader moreDataHeader = new BasicHeader(BuildWithDetails.MORE_DATA_HEADER, "false");
75+
BasicHeader textSizeHeader = new BasicHeader(BuildWithDetails.TEXT_SIZE_HEADER, Integer.toString(textLength));
76+
given(response.getFirstHeader(BuildWithDetails.MORE_DATA_HEADER)).willReturn(moreDataHeader);
77+
given(response.getFirstHeader(BuildWithDetails.TEXT_SIZE_HEADER)).willReturn(textSizeHeader);
78+
given(client.post_form_with_result(anyString(),anyListOf(NameValuePair.class),anyBoolean())).willReturn(response);
79+
buildWithDetails.streamConsoleOutput(new BuildConsoleStreamListener() {
80+
@Override
81+
public void onData(String newLogChunk) {
82+
assertThat(newLogChunk).isEqualTo(text);
83+
}
84+
85+
@Override
86+
public void onError(Exception error) {
87+
fail("No error expected",error);
88+
}
89+
90+
@Override
91+
public void finished() {
92+
}
93+
},1,2);
94+
95+
96+
} catch (IOException e) {
97+
fail("Should not return exception",e);
98+
}
99+
}
100+
}

0 commit comments

Comments
 (0)