extends StepExecution {
33 |
34 | @Getter
35 | private final transient TaskListener listener;
36 | @Getter
37 | private final transient Launcher launcher;
38 | private static ExecutorService executorService;
39 | @Getter
40 | private final BasicSSHStep step;
41 |
42 | private transient volatile Future> task;
43 | private transient String threadName;
44 | private transient Throwable stopCause;
45 |
46 | protected SSHStepExecution(BasicSSHStep step, @NonNull StepContext context)
47 | throws IOException, InterruptedException {
48 | super(context);
49 | listener = context.get(TaskListener.class);
50 | launcher = context.get(Launcher.class);
51 | this.step = step;
52 | }
53 |
54 | static synchronized ExecutorService getExecutorService() {
55 | if (executorService == null) {
56 | executorService = Executors.newCachedThreadPool(
57 | new NamingThreadFactory(new ClassLoaderSanityThreadFactory(new DaemonThreadFactory()),
58 | "org.jenkinsci.plugins.ssh.util.SSHStepExecution"));
59 | }
60 | return executorService;
61 | }
62 |
63 | /**
64 | * Meat of the execution.
65 | *
66 | * When this method returns, a step execution is over.
67 | */
68 | protected abstract T run() throws Exception;
69 |
70 | protected VirtualChannel getChannel() {
71 | final VirtualChannel channel = getLauncher().getChannel();
72 | if (channel == null) {
73 | throw new IllegalArgumentException(
74 | "Unable to get the channel, Perhaps you forgot to surround the code with a step that provides this, such as: node, dockerNode");
75 | }
76 | return channel;
77 | }
78 |
79 | @Override
80 | public final boolean start() {
81 | Authentication auth = Jenkins.getAuthentication2();
82 | task = getExecutorService().submit(() -> {
83 | threadName = Thread.currentThread().getName();
84 | try {
85 | MDC.put("execution.id", UUID.randomUUID().toString());
86 | T ret;
87 | try (ACLContext acl = ACL.as2(auth)) {
88 | ret = run();
89 | }
90 | getContext().onSuccess(ret);
91 | } catch (Throwable x) {
92 | if (stopCause == null) {
93 | getContext().onFailure(x);
94 | } else {
95 | stopCause.addSuppressed(x);
96 | }
97 | } finally {
98 | MDC.clear();
99 | }
100 | });
101 | return false;
102 | }
103 |
104 | /**
105 | * If the computation is going synchronously, try to cancel that.
106 | */
107 | @Override
108 | public void stop(@NonNull Throwable cause) throws Exception {
109 | if (task != null) {
110 | stopCause = cause;
111 | task.cancel(true);
112 | }
113 | super.stop(cause);
114 | }
115 |
116 | @Override
117 | public void onResume() {
118 | getContext().onFailure(
119 | new Exception("Resume after a restart not supported for non-blocking synchronous steps"));
120 | }
121 |
122 | @Override
123 | public @NonNull
124 | String getStatus() {
125 | if (threadName != null) {
126 | return "running in thread: " + threadName;
127 | } else {
128 | return "not yet scheduled";
129 | }
130 | }
131 |
132 | }
133 |
--------------------------------------------------------------------------------
/src/main/resources/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 | Jenkins pipeline steps which provides SSH facilities such as command execution or file transfer for continuous delivery.
4 |
5 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/sshsteps/steps/CommandStep/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | This is a special step. No snippet generation available. See inline help or docs on the README
8 | for more information.
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/sshsteps/steps/CommandStep/help.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Takes a remote (map) of settings and command
to execute it on the remote node and
4 | returns output.
5 | See docs on the README for more information.
7 |
8 |
9 | def remote = [:]
10 | remote.name = 'test'
11 | remote.host = 'test.domain.com'
12 | remote.user = 'root'
13 | remote.password = 'password'
14 | remote.allowAnyHosts = true
15 | stage('Remote SSH') {
16 | sshCommand remote: remote, command: "ls -lrt"
17 | sshCommand remote: remote, command: "for i in {1..5}; do echo -n \"Loop \$i \"; date ; sleep 1; done"
18 | }
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/sshsteps/steps/GetStep/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | This is a special step. No snippet generation available. See inline help or docs on the README
8 | for more information.
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/sshsteps/steps/GetStep/help.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Takes a remote (map) of settings, local file/directory into
to get the given
4 | file/directory from
remote node.
5 | See docs on the README for more information.
7 |
8 |
9 | def remote = [:]
10 | remote.name = 'test'
11 | remote.host = 'test.domain.com'
12 | remote.user = 'root'
13 | remote.password = 'password'
14 | remote.allowAnyHosts = true
15 | stage('Remote SSH') {
16 | sshGet remote: remote, from: 'abc.sh', into: 'abc_get.sh', override: true
17 | }
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/sshsteps/steps/PutStep/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | This is a special step. No snippet generation available. See inline help or docs on the README
8 | for more information.
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/sshsteps/steps/PutStep/help.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Takes a remote (map) of settings, local file/directory from
workspace and path to
4 | put this into
remote node.
5 | See docs on the README for more information.
7 |
8 |
9 | def remote = [:]
10 | remote.name = 'test'
11 | remote.host = 'test.domain.com'
12 | remote.user = 'root'
13 | remote.password = 'password'
14 | remote.allowAnyHosts = true
15 | stage('Remote SSH') {
16 | writeFile file: 'abc.sh', text: 'ls -lrt'
17 | sshPut remote: remote, from: 'abc.sh', into: '.'
18 | }
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/sshsteps/steps/RemoveStep/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | This is a special step. No snippet generation available. See inline help or docs on the README
8 | for more information.
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/sshsteps/steps/RemoveStep/help.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Takes a remote (map) of settings and path
(file/directory) to remove from remote
4 | node.
5 | See docs on the README for more information.
7 |
8 |
9 | def remote = [:]
10 | remote.name = 'test'
11 | remote.host = 'test.domain.com'
12 | remote.user = 'root'
13 | remote.password = 'password'
14 | remote.allowAnyHosts = true
15 | stage('Remote SSH') {
16 | sshRemove remote: remote, path: "abc.sh"
17 | }
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/sshsteps/steps/ScriptStep/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | This is a special step. No snippet generation available. See inline help or docs on the README
8 | for more information.
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/sshsteps/steps/ScriptStep/help.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Takes a remote (map) of settings and script a shell script file to execute it on the remote node
4 | and returns output.
5 | See docs on the README for more information.
7 |
8 |
9 | def remote = [:]
10 | remote.name = 'test'
11 | remote.host = 'test.domain.com'
12 | remote.user = 'root'
13 | remote.password = 'password'
14 | remote.allowAnyHosts = true
15 | stage('Remote SSH') {
16 | writeFile file: 'abc.sh', text: 'ls -lrt'
17 | sshScript remote: remote, script: "abc.sh"
18 | }
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/sshsteps/SSHServiceTest.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.plugins.sshsteps;
2 |
3 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 | import org.junit.jupiter.api.Test;
8 |
9 | /**
10 | * Test cases for SSHService.
11 | *
12 | * @author Naresh Rayapati.
13 | */
14 | class SSHServiceTest {
15 |
16 | @Test
17 | void testWithEmptyRemoteThrowsAssertionError() {
18 | Map remote = new HashMap<>();
19 |
20 | assertThatExceptionOfType(AssertionError.class)
21 | .isThrownBy(() -> SSHService.create(remote, false, false, null))
22 | .withMessage("SSH Steps: remote is null or empty. Expression: remote. Values: remote = [:]")
23 | .withStackTraceContaining("AssertionError")
24 | .withNoCause();
25 | }
26 |
27 | @Test
28 | void testRemoteWithEmptyNameThrowsAssertionError() {
29 | Map remote = new HashMap<>();
30 | remote.put("name", "");
31 |
32 | assertThatExceptionOfType(AssertionError.class)
33 | .isThrownBy(() -> SSHService.create(remote, false, false, null))
34 | .withMessage(
35 | "SSH Steps: a remote (or a gateway) is missing the required field 'name'. Expression: remote.name")
36 | .withStackTraceContaining("AssertionError")
37 | .withNoCause();
38 | }
39 |
40 | @Test
41 | void testRemoteWithEmptyUserThrowsAssertionError() {
42 | Map remote = new HashMap<>();
43 | remote.put("name", "dummy");
44 |
45 | assertThatExceptionOfType(AssertionError.class)
46 | .isThrownBy(() -> SSHService.create(remote, false, false, null))
47 | .withMessage("SSH Steps: user must be given (dummy). Expression: remote.user")
48 | .withStackTraceContaining("AssertionError")
49 | .withNoCause();
50 | }
51 |
52 | @Test
53 | void testRemoteWithOutKnownHostsAndAllowAnyHostsThrowsIllegalArgumentException() {
54 | Map remote = new HashMap<>();
55 | remote.put("name", "dummy");
56 | remote.put("user", "dummy");
57 |
58 | assertThatExceptionOfType(IllegalArgumentException.class)
59 | .isThrownBy(() -> SSHService.create(remote, false, false, null))
60 | .withMessage("SSH Steps: knownHosts must be provided when allowAnyHosts is false: dummy")
61 | .withStackTraceContaining("IllegalArgumentException")
62 | .withNoCause();
63 | }
64 |
65 | @Test
66 | void testRemoteWithMinimumRequiredParams() {
67 | Map remote = new HashMap<>();
68 | remote.put("name", "dummy");
69 | remote.put("user", "dummy");
70 | remote.put("allowAnyHosts", true);
71 |
72 | SSHService.create(remote, false, false, null);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/sshsteps/steps/BaseTest.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.plugins.sshsteps.steps;
2 |
3 | import hudson.EnvVars;
4 | import hudson.Launcher;
5 | import hudson.model.Run;
6 | import hudson.model.TaskListener;
7 | import org.jenkinsci.plugins.sshsteps.SSHService;
8 | import org.jenkinsci.plugins.sshsteps.util.TestVirtualChannel;
9 | import org.jenkinsci.plugins.workflow.steps.StepContext;
10 | import org.junit.jupiter.api.AfterEach;
11 | import org.junit.jupiter.api.BeforeEach;
12 | import org.mockito.Mock;
13 | import org.mockito.MockedStatic;
14 | import org.mockito.Mockito;
15 | import org.mockito.MockitoAnnotations;
16 |
17 | import java.io.IOException;
18 | import java.io.PrintStream;
19 |
20 | import static org.mockito.ArgumentMatchers.any;
21 | import static org.mockito.ArgumentMatchers.anyBoolean;
22 | import static org.mockito.Mockito.doNothing;
23 | import static org.mockito.Mockito.when;
24 |
25 | /**
26 | * Base Test Class.
27 | *
28 | * @author Naresh Rayapati
29 | */
30 | class BaseTest {
31 |
32 | @Mock
33 | TaskListener taskListenerMock;
34 | @Mock
35 | Run, ?> runMock;
36 | @Mock
37 | EnvVars envVarsMock;
38 | @Mock
39 | PrintStream printStreamMock;
40 | @Mock
41 | SSHService sshServiceMock;
42 | @Mock
43 | StepContext contextMock;
44 | @Mock
45 | Launcher launcherMock;
46 |
47 | private AutoCloseable closeable;
48 | private MockedStatic sshService;
49 |
50 | @BeforeEach
51 | void setUpBase() throws IOException, InterruptedException {
52 |
53 | closeable = MockitoAnnotations.openMocks(this);
54 |
55 | when(runMock.getCauses()).thenReturn(null);
56 | when(taskListenerMock.getLogger()).thenReturn(printStreamMock);
57 | doNothing().when(printStreamMock).println();
58 | when(launcherMock.getChannel()).thenReturn(new TestVirtualChannel());
59 |
60 | sshService = Mockito.mockStatic(SSHService.class);
61 | sshService.when(() -> SSHService.create(any(), anyBoolean(), anyBoolean(), any())).thenReturn(sshServiceMock);
62 |
63 | when(contextMock.get(Run.class)).thenReturn(runMock);
64 | when(contextMock.get(TaskListener.class)).thenReturn(taskListenerMock);
65 | when(contextMock.get(EnvVars.class)).thenReturn(envVarsMock);
66 | when(contextMock.get(Launcher.class)).thenReturn(launcherMock);
67 | }
68 |
69 | @AfterEach
70 | void tearUpBase() throws Exception {
71 | sshService.close();
72 | closeable.close();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/sshsteps/steps/CommandStepTest.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.plugins.sshsteps.steps;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
6 | import static org.mockito.Mockito.times;
7 | import static org.mockito.Mockito.verify;
8 |
9 | /**
10 | * Unit test cases for CommandStep class.
11 | *
12 | * @author Naresh Rayapati
13 | */
14 | class CommandStepTest extends BaseTest {
15 |
16 | CommandStep.Execution stepExecution;
17 |
18 | @Test
19 | void testWithEmptyCommandThrowsIllegalArgumentException() throws Exception {
20 | final CommandStep step = new CommandStep("");
21 | stepExecution = new CommandStep.Execution(step, contextMock);
22 |
23 | // Execute and assert Test.
24 | assertThatExceptionOfType(IllegalArgumentException.class)
25 | .isThrownBy(() -> stepExecution.run())
26 | .withMessage("command is null or empty")
27 | .withStackTraceContaining("IllegalArgumentException")
28 | .withNoCause();
29 | }
30 |
31 | @Test
32 | void testSuccessfulExecuteCommand() throws Exception {
33 | final CommandStep step = new CommandStep("ls -lrt");
34 |
35 | // Since SSHService is a mock, it is not validating remote.
36 | stepExecution = new CommandStep.Execution(step, contextMock);
37 |
38 | // Execute Test.
39 | stepExecution.run();
40 |
41 | // Assert Test
42 | verify(sshServiceMock, times(1)).executeCommand("ls -lrt", false);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/sshsteps/steps/GetStepTest.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.plugins.sshsteps.steps;
2 |
3 | import hudson.FilePath;
4 | import org.junit.jupiter.api.BeforeEach;
5 | import org.junit.jupiter.api.Test;
6 | import org.mockito.Mock;
7 |
8 | import java.io.IOException;
9 |
10 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
11 | import static org.mockito.Mockito.any;
12 | import static org.mockito.Mockito.times;
13 | import static org.mockito.Mockito.verify;
14 | import static org.mockito.Mockito.when;
15 |
16 | /**
17 | * Unit test cases for GetStep class.
18 | *
19 | * @author Naresh Rayapati
20 | */
21 | class GetStepTest extends BaseTest {
22 |
23 | final String path = "test.sh";
24 | final String filterBy = "name";
25 | final String filterRegex = null;
26 |
27 | @Mock
28 | FilePath filePathMock;
29 |
30 | GetStep.Execution stepExecution;
31 |
32 | @BeforeEach
33 | void setup() throws IOException, InterruptedException {
34 |
35 | when(filePathMock.child(any())).thenReturn(filePathMock);
36 | when(filePathMock.exists()).thenReturn(true);
37 | when(filePathMock.isDirectory()).thenReturn(false);
38 | when(filePathMock.getRemote()).thenReturn(path);
39 |
40 | when(contextMock.get(FilePath.class)).thenReturn(filePathMock);
41 |
42 | }
43 |
44 | @Test
45 | void testWithEmptyFromThrowsIllegalArgumentException() throws Exception {
46 | final GetStep step = new GetStep("", path);
47 | stepExecution = new GetStep.Execution(step, contextMock);
48 |
49 | // Execute and assert Test.
50 | assertThatExceptionOfType(IllegalArgumentException.class)
51 | .isThrownBy(() -> stepExecution.run())
52 | .withMessage("from is null or empty")
53 | .withStackTraceContaining("IllegalArgumentException")
54 | .withNoCause();
55 | }
56 |
57 | @Test
58 | void testWithEmptyIntoThrowsIllegalArgumentException() throws Exception {
59 | final GetStep step = new GetStep(path, "");
60 | step.setOverride(true);
61 | stepExecution = new GetStep.Execution(step, contextMock);
62 |
63 | // Execute and assert Test.
64 | assertThatExceptionOfType(IllegalArgumentException.class)
65 | .isThrownBy(() -> stepExecution.run())
66 | .withMessage("into is null or empty")
67 | .withStackTraceContaining("IllegalArgumentException")
68 | .withNoCause();
69 | }
70 |
71 | @Test
72 | void testSuccessfulExecuteScript() throws Exception {
73 | final GetStep step = new GetStep(path, path);
74 | step.setOverride(true);
75 |
76 | // Since SSHService is a mock, it is not validating remote.
77 | stepExecution = new GetStep.Execution(step, contextMock);
78 |
79 | // Execute Test.
80 | stepExecution.run();
81 |
82 | // Assert Test
83 | verify(sshServiceMock, times(1)).get(path, path, filterBy, filterRegex);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/sshsteps/steps/PutStepTest.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.plugins.sshsteps.steps;
2 |
3 | import hudson.FilePath;
4 | import org.junit.jupiter.api.BeforeEach;
5 | import org.junit.jupiter.api.Test;
6 | import org.mockito.Mock;
7 |
8 | import java.io.IOException;
9 |
10 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
11 | import static org.mockito.Mockito.any;
12 | import static org.mockito.Mockito.times;
13 | import static org.mockito.Mockito.verify;
14 | import static org.mockito.Mockito.when;
15 |
16 | /**
17 | * Unit test cases for PutStep class.
18 | *
19 | * @author Naresh Rayapati
20 | */
21 | class PutStepTest extends BaseTest {
22 |
23 | final String path = "test.sh";
24 | final String filterBy = "name";
25 | final String filterRegex = null;
26 |
27 | @Mock
28 | FilePath filePathMock;
29 |
30 | PutStep.Execution stepExecution;
31 |
32 | @BeforeEach
33 | void setup() throws IOException, InterruptedException {
34 |
35 | when(filePathMock.child(any())).thenReturn(filePathMock);
36 | when(filePathMock.exists()).thenReturn(true);
37 | when(filePathMock.isDirectory()).thenReturn(false);
38 | when(filePathMock.getRemote()).thenReturn(path);
39 |
40 | when(contextMock.get(FilePath.class)).thenReturn(filePathMock);
41 |
42 | }
43 |
44 | @Test
45 | void testWithEmptyFromThrowsIllegalArgumentException() throws Exception {
46 | final PutStep step = new PutStep("", path);
47 | stepExecution = new PutStep.Execution(step, contextMock);
48 |
49 | // Execute and assert Test.
50 | assertThatExceptionOfType(IllegalArgumentException.class)
51 | .isThrownBy(() -> stepExecution.run())
52 | .withMessage("from is null or empty")
53 | .withStackTraceContaining("IllegalArgumentException")
54 | .withNoCause();
55 | }
56 |
57 | @Test
58 | void testWithEmptyIntoThrowsIllegalArgumentException() throws Exception {
59 | final PutStep step = new PutStep(path, "");
60 | stepExecution = new PutStep.Execution(step, contextMock);
61 |
62 | // Execute and assert Test.
63 | assertThatExceptionOfType(IllegalArgumentException.class)
64 | .isThrownBy(() -> stepExecution.run())
65 | .withMessage("into is null or empty")
66 | .withStackTraceContaining("IllegalArgumentException")
67 | .withNoCause();
68 | }
69 |
70 | @Test
71 | void testSuccessfulPut() throws Exception {
72 | final PutStep step = new PutStep(path, path);
73 |
74 | // Since SSHService is a mock, it is not validating remote.
75 | stepExecution = new PutStep.Execution(step, contextMock);
76 |
77 | // Execute Test.
78 | stepExecution.run();
79 |
80 | // Assert Test
81 | verify(sshServiceMock, times(1)).put(path, path, filterBy, filterRegex);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/sshsteps/steps/RemoveStepTest.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.plugins.sshsteps.steps;
2 |
3 | import hudson.FilePath;
4 | import org.junit.jupiter.api.BeforeEach;
5 | import org.junit.jupiter.api.Test;
6 | import org.mockito.Mock;
7 |
8 | import java.io.IOException;
9 |
10 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
11 | import static org.mockito.Mockito.any;
12 | import static org.mockito.Mockito.times;
13 | import static org.mockito.Mockito.verify;
14 | import static org.mockito.Mockito.when;
15 |
16 | /**
17 | * Unit test cases for RemoveStep class.
18 | *
19 | * @author Naresh Rayapati
20 | */
21 | class RemoveStepTest extends BaseTest {
22 |
23 | final String path = "test.sh";
24 |
25 | @Mock
26 | FilePath filePathMock;
27 |
28 | RemoveStep.Execution stepExecution;
29 |
30 | @BeforeEach
31 | void setup() throws IOException, InterruptedException {
32 |
33 | when(filePathMock.child(any())).thenReturn(filePathMock);
34 | when(filePathMock.exists()).thenReturn(true);
35 | when(filePathMock.isDirectory()).thenReturn(false);
36 | when(filePathMock.getRemote()).thenReturn(path);
37 |
38 | when(contextMock.get(FilePath.class)).thenReturn(filePathMock);
39 |
40 | }
41 |
42 | @Test
43 | void testWithEmptyPathThrowsIllegalArgumentException() throws Exception {
44 | final RemoveStep step = new RemoveStep("");
45 | stepExecution = new RemoveStep.Execution(step, contextMock);
46 |
47 | // Execute and assert Test.
48 | assertThatExceptionOfType(IllegalArgumentException.class)
49 | .isThrownBy(() -> stepExecution.run())
50 | .withMessage("path is null or empty")
51 | .withStackTraceContaining("IllegalArgumentException")
52 | .withNoCause();
53 | }
54 |
55 | @Test
56 | void testSuccessfulRemove() throws Exception {
57 | final RemoveStep step = new RemoveStep(path);
58 |
59 | // Since SSHService is a mock, it is not validating remote.
60 | stepExecution = new RemoveStep.Execution(step, contextMock);
61 |
62 | // Execute Test.
63 | stepExecution.run();
64 |
65 | // Assert Test
66 | verify(sshServiceMock, times(1)).remove(path);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/sshsteps/steps/ScriptStepTest.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.plugins.sshsteps.steps;
2 |
3 | import hudson.FilePath;
4 | import org.junit.jupiter.api.BeforeEach;
5 | import org.junit.jupiter.api.Test;
6 | import org.mockito.Mock;
7 |
8 | import java.io.IOException;
9 |
10 | import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
11 | import static org.mockito.Mockito.any;
12 | import static org.mockito.Mockito.times;
13 | import static org.mockito.Mockito.verify;
14 | import static org.mockito.Mockito.when;
15 |
16 | /**
17 | * Unit test cases for ScriptStep class.
18 | *
19 | * @author Naresh Rayapati
20 | */
21 | class ScriptStepTest extends BaseTest {
22 |
23 | final String scriptName = "test.sh";
24 |
25 | @Mock
26 | FilePath filePathMock;
27 |
28 | ScriptStep.Execution stepExecution;
29 |
30 | @BeforeEach
31 | void setup() throws IOException, InterruptedException {
32 |
33 | when(filePathMock.child(any())).thenReturn(filePathMock);
34 | when(filePathMock.exists()).thenReturn(true);
35 | when(filePathMock.isDirectory()).thenReturn(false);
36 | when(filePathMock.getRemote()).thenReturn(scriptName);
37 |
38 | when(contextMock.get(FilePath.class)).thenReturn(filePathMock);
39 |
40 | }
41 |
42 | @Test
43 | void testWithEmptyCommandThrowsIllegalArgumentException() throws Exception {
44 | final ScriptStep step = new ScriptStep("");
45 | stepExecution = new ScriptStep.Execution(step, contextMock);
46 |
47 | // Execute and assert Test.
48 | assertThatExceptionOfType(IllegalArgumentException.class)
49 | .isThrownBy(() -> stepExecution.run())
50 | .withMessage("script is null or empty")
51 | .withStackTraceContaining("IllegalArgumentException")
52 | .withNoCause();
53 | }
54 |
55 | @Test
56 | void testSuccessfulExecuteScript() throws Exception {
57 | final ScriptStep step = new ScriptStep(scriptName);
58 |
59 | // Since SSHService is a mock, it is not validating remote.
60 | stepExecution = new ScriptStep.Execution(step, contextMock);
61 |
62 | // Execute Test.
63 | stepExecution.run();
64 |
65 | // Assert Test
66 | verify(sshServiceMock, times(1)).executeScriptFromFile(scriptName);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/sshsteps/util/TestVirtualChannel.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.plugins.sshsteps.util;
2 |
3 | import hudson.remoting.Callable;
4 | import hudson.remoting.Future;
5 | import hudson.remoting.VirtualChannel;
6 |
7 | /**
8 | * VirtualChannel for testing.
9 | *
10 | * @author Naresh Rayapati
11 | */
12 | public class TestVirtualChannel implements VirtualChannel {
13 |
14 | @Override
15 | public V call(Callable callable) throws T {
16 | return callable.call();
17 | }
18 |
19 | @Override
20 | public Future callAsync(Callable callable) {
21 | return null;
22 | }
23 |
24 | @Override
25 | public void close() {
26 |
27 | }
28 |
29 | @Override
30 | public void join() {
31 |
32 | }
33 |
34 | @Override
35 | public void join(long timeout) {
36 |
37 | }
38 |
39 | @Override
40 | public T export(Class type, T instance) {
41 | return null;
42 | }
43 |
44 | @Override
45 | public void syncLocalIO() {
46 |
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/test/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | # Root logger option
2 | log4j.rootLogger=INFO, stdout
3 | # Direct log messages to stdout
4 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender
5 | log4j.appender.stdout.Target=System.out
6 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
7 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1} - %m%n
8 |
--------------------------------------------------------------------------------