Skip to content

Commit 4ec3349

Browse files
authored
Extract close quietly body execution callback (#1698)
1 parent e05368a commit 4ec3349

File tree

3 files changed

+128
-23
lines changed

3 files changed

+128
-23
lines changed

src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/ContainerStepExecution.java

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,11 @@
1212
import hudson.slaves.NodeProperty;
1313
import hudson.slaves.NodePropertyDescriptor;
1414
import hudson.util.DescribableList;
15-
import java.io.Closeable;
1615
import java.util.Collections;
1716
import java.util.List;
1817
import java.util.logging.Level;
1918
import java.util.logging.Logger;
2019
import jenkins.model.Jenkins;
21-
import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback;
2220
import org.jenkinsci.plugins.workflow.steps.BodyInvoker;
2321
import org.jenkinsci.plugins.workflow.steps.EnvironmentExpander;
2422
import org.jenkinsci.plugins.workflow.steps.StepContext;
@@ -81,7 +79,7 @@ public boolean start() throws Exception {
8179
.newBodyInvoker()
8280
.withContexts(
8381
BodyInvoker.mergeLauncherDecorators(getContext().get(LauncherDecorator.class), decorator), env)
84-
.withCallback(new ContainerExecCallback(decorator))
82+
.withCallback(closeQuietlyCallback(decorator))
8583
.start();
8684
return false;
8785
}
@@ -91,21 +89,4 @@ public void stop(@NonNull Throwable cause) throws Exception {
9189
LOGGER.log(Level.FINE, "Stopping container step.");
9290
closeQuietly(getContext(), decorator);
9391
}
94-
95-
@SuppressFBWarnings("SE_BAD_FIELD")
96-
private static class ContainerExecCallback extends BodyExecutionCallback.TailCall {
97-
98-
private static final long serialVersionUID = 6385838254761750483L;
99-
100-
private final Closeable[] closeables;
101-
102-
private ContainerExecCallback(Closeable... closeables) {
103-
this.closeables = closeables;
104-
}
105-
106-
@Override
107-
public void finished(StepContext context) {
108-
closeQuietly(context, closeables);
109-
}
110-
}
11192
}

src/main/java/org/csanchez/jenkins/plugins/kubernetes/pipeline/Resources.java

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,78 @@
1616

1717
package org.csanchez.jenkins.plugins.kubernetes.pipeline;
1818

19+
import edu.umd.cs.findbugs.annotations.NonNull;
20+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
1921
import hudson.model.TaskListener;
2022
import java.io.Closeable;
2123
import java.io.IOException;
2224
import java.util.logging.Level;
2325
import java.util.logging.Logger;
26+
import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback;
2427
import org.jenkinsci.plugins.workflow.steps.StepContext;
2528

2629
public class Resources {
27-
private static final transient Logger LOGGER = Logger.getLogger(ContainerStepExecution.class.getName());
2830

29-
static void closeQuietly(StepContext context, Closeable... closeables) {
31+
private static final Logger LOGGER = Logger.getLogger(ContainerStepExecution.class.getName());
32+
33+
/**
34+
* Close each {@link Closeable}, capturing and ignoring any {@link IOException} thrown.
35+
* @param context step context
36+
* @param closeables list of closable resources to close
37+
*/
38+
public static void closeQuietly(@NonNull StepContext context, @NonNull Closeable... closeables) {
3039
for (Closeable c : closeables) {
3140
if (c != null) {
3241
try {
3342
c.close();
3443
} catch (IOException e) {
3544
try {
36-
context.get(TaskListener.class).error("Error while closing: [" + c + "]");
45+
TaskListener listener = context.get(TaskListener.class);
46+
if (listener != null) {
47+
listener.error("Error while closing: [" + c + "]");
48+
}
3749
} catch (IOException | InterruptedException e1) {
3850
LOGGER.log(Level.WARNING, "Error writing to task listener", e);
3951
}
4052
}
4153
}
4254
}
4355
}
56+
57+
/**
58+
* Factory method for a {@link BodyExecutionCallback} that closes each {@link Closeable} when
59+
* execution is finished.
60+
* @param closeables list of closable resources
61+
* @return new body execution callback, never null
62+
*/
63+
@NonNull
64+
public static BodyExecutionCallback closeQuietlyCallback(@NonNull Closeable... closeables) {
65+
return new CloseQuietlyBodyExecutionCallback(closeables);
66+
}
67+
68+
/**
69+
* Pipeline body execution callback that quietly closes all referenced {@link Closeable} when
70+
* {@link #finished(StepContext)} is called.
71+
* @see #closeQuietly(StepContext, Closeable...)
72+
*/
73+
@SuppressFBWarnings("SE_BAD_FIELD")
74+
private static class CloseQuietlyBodyExecutionCallback extends BodyExecutionCallback.TailCall {
75+
76+
private static final long serialVersionUID = 6385838254761750483L;
77+
78+
private final Closeable[] closeables;
79+
80+
/**
81+
* Create new body execution callback that closes the provided resources quietly.
82+
* @param closeables list of closable resources
83+
*/
84+
CloseQuietlyBodyExecutionCallback(@NonNull Closeable... closeables) {
85+
this.closeables = closeables;
86+
}
87+
88+
@Override
89+
public void finished(StepContext context) {
90+
closeQuietly(context, closeables);
91+
}
92+
}
4493
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package org.csanchez.jenkins.plugins.kubernetes.pipeline;
2+
3+
import static org.mockito.Mockito.*;
4+
5+
import hudson.AbortException;
6+
import hudson.model.TaskListener;
7+
import java.io.Closeable;
8+
import java.io.IOException;
9+
import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback;
10+
import org.jenkinsci.plugins.workflow.steps.StepContext;
11+
import org.junit.Test;
12+
13+
public class ResourcesTest {
14+
15+
@Test
16+
public void testCloseQuietly() throws Exception {
17+
StepContext ctx = mock(StepContext.class);
18+
TaskListener listener = mock(TaskListener.class);
19+
when(ctx.get(TaskListener.class))
20+
.thenReturn(listener)
21+
.thenThrow(IOException.class)
22+
.thenThrow(InterruptedException.class)
23+
.thenReturn(null);
24+
Closeable c1 = mock(Closeable.class);
25+
doThrow(IOException.class).when(c1).close();
26+
Closeable c2 = mock(Closeable.class);
27+
doThrow(IOException.class).when(c2).close();
28+
Closeable c3 = mock(Closeable.class);
29+
doThrow(IOException.class).when(c3).close();
30+
Closeable c4 = mock(Closeable.class);
31+
32+
// test
33+
Resources.closeQuietly(ctx, c1, c2, null, c3, c4);
34+
35+
// verify
36+
verify(c1).close();
37+
verify(c2).close();
38+
verify(c3).close();
39+
verify(c4).close();
40+
verify(ctx, times(3)).get(TaskListener.class);
41+
verify(listener).error(any());
42+
}
43+
44+
@Test
45+
public void testCloseQuietlyCallbackOnSuccess() throws Exception {
46+
StepContext ctx = mock(StepContext.class);
47+
Closeable c1 = mock(Closeable.class);
48+
doThrow(IOException.class).when(c1).close();
49+
Closeable c2 = mock(Closeable.class);
50+
51+
// test
52+
BodyExecutionCallback callback = Resources.closeQuietlyCallback(c1, c2);
53+
callback.onSuccess(ctx, "done");
54+
55+
// verify
56+
verify(c1).close();
57+
verify(c2).close();
58+
}
59+
60+
@Test
61+
public void testCloseQuietlyCallbackOnFailure() throws Exception {
62+
StepContext ctx = mock(StepContext.class);
63+
Closeable c1 = mock(Closeable.class);
64+
doThrow(IOException.class).when(c1).close();
65+
Closeable c2 = mock(Closeable.class);
66+
67+
// test
68+
BodyExecutionCallback callback = Resources.closeQuietlyCallback(c1, c2);
69+
callback.onFailure(ctx, new AbortException());
70+
71+
// verify
72+
verify(c1).close();
73+
verify(c2).close();
74+
}
75+
}

0 commit comments

Comments
 (0)