ssh-client-wrapper 1.8.3 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,24 @@
1
+ name: report coverage data to code climate
2
+ on:
3
+ push:
4
+ tags:
5
+ - 'v*'
6
+ jobs:
7
+ test:
8
+ uses: ./.github/workflows/run_test.yml
9
+ coverage:
10
+ needs:
11
+ - test
12
+ name: coverage report
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v3
16
+ - uses: actions/download-artifact@v3
17
+ with:
18
+ name: code-coverage-report
19
+ - uses: paambaati/codeclimate-action@v5.0.0
20
+ env:
21
+ CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
22
+ with:
23
+ coverageLocations: "coverage-report.lcov:lcov"
24
+ debug: true
@@ -14,9 +14,9 @@ jobs:
14
14
  - uses: actions/checkout@v3
15
15
  - uses: actions/setup-node@v3
16
16
  with:
17
- node-version: 18
17
+ node-version: 20
18
18
  registry-url: https://registry.npmjs.org/
19
19
  - run: npm ci
20
20
  - run: npm publish
21
21
  env:
22
- NODE_AUTH_TOKEN: ${{secrets.npm_token}}
22
+ NODE_AUTH_TOKEN: ${{ secrets.npm_token }}
@@ -34,3 +34,9 @@ jobs:
34
34
  node-version: ${{ matrix.node-version }}
35
35
  - run: npm ci
36
36
  - run: npm run test
37
+ - run: npm run coverage
38
+ - uses: actions/upload-artifact@v3
39
+ with:
40
+ name: code-coverage-report
41
+ path: coverage-report.lcov
42
+ retention-days: 1
package/lib/fork.js CHANGED
@@ -5,7 +5,7 @@ const debugMasterVerbose = require("debug")("sshClientWrapper:insecure:fork_mast
5
5
  const debugSendPty = require("debug")("sshClientWrapper:insecure:fork_sendpty");
6
6
  const { spawn } = require("node-pty");
7
7
 
8
- const { rePwPrompt, rePhPrompt, reNewHostPrompt, buff2String, sendPty } = require("./util.js");
8
+ const { rePwPrompt, rePhPrompt, reNewHostPrompt, buff2String, sendPty, watchDogTimer } = require("./util.js");
9
9
 
10
10
  const nodePtyOpt = {
11
11
  shell: process.platform === "win32" ? process.env.ComSpec : process.env.SHELL,
@@ -67,11 +67,19 @@ function getMasterPty(hostInfo, logger) {
67
67
  hostInfo.masterPty = ptyProcess;
68
68
  }
69
69
 
70
- async function fork(hostInfo, cmd, args, outputCallback) {
71
- let ptyProcess = null;
72
- const p = new Promise((resolve, reject)=>{
70
+ async function fork(hostInfo, cmd, args, timeout, outputCallback) {
71
+ return new Promise((resolve, reject)=>{
73
72
  debug(`cmd=${cmd}: args=${args}`);
74
- ptyProcess = spawn(cmd, args, nodePtyOpt);
73
+ const ptyProcess = spawn(cmd, args, nodePtyOpt);
74
+ let ac = null;
75
+ if (timeout > 0) {
76
+ ac = watchDogTimer(timeout, cmd, ()=>{
77
+ ptyProcess.kill();
78
+ const err = new Error(`watchdog timer expired ${timeout}`);
79
+ err.code = "TIMEEXPIRE";
80
+ reject(err);
81
+ });
82
+ }
75
83
  ptyProcess.onData(buff2String.bind(null, debugVerbose));
76
84
  ptyProcess.onData((data)=>{
77
85
  const output = data.toString();
@@ -91,6 +99,10 @@ async function fork(hostInfo, cmd, args, outputCallback) {
91
99
  ptyProcess.onExit(({ exitCode, signal })=>{
92
100
  debug("onExit", exitCode, signal);
93
101
 
102
+ if (ac !== null) {
103
+ ac.abort();
104
+ }
105
+
94
106
  if (signal > 0) {
95
107
  const err = new Error("signal caught");
96
108
  err.signal = signal;
@@ -104,8 +116,6 @@ async function fork(hostInfo, cmd, args, outputCallback) {
104
116
  return reject(err);
105
117
  });
106
118
  });
107
- p.pty = ptyProcess;
108
- return p;
109
119
  }
110
120
  module.exports = {
111
121
  fork,
package/lib/index.js CHANGED
@@ -30,6 +30,11 @@ const { isArrayOfString, sanityCheck } = require("./util.js");
30
30
  * @param {string[]} hostInfo.sshOpt - additional options for ssh
31
31
  */
32
32
 
33
+ const logAndReject = (message)=>{
34
+ debug(message);
35
+ return Promise.reject(new Error(message));
36
+ };
37
+
33
38
  /**
34
39
  * Facade class.
35
40
  * @constructor
@@ -46,18 +51,19 @@ class SshClientWrapper {
46
51
  /**
47
52
  * Execute command on remote host.
48
53
  * @param {string} cmd - Cmdline which will be executed.
54
+ * @param {Integer} timeout - timeout in secconds
49
55
  * @param { Function } outputCallback - call back routine for output
50
56
  * @returns {Integer} - Return code of cmd.
51
57
  *
52
58
  * If stdout and stderr is array, last 5 line of stdout and stderr is stored in them.
53
59
  */
54
- async exec(cmd, outputCallback) {
60
+ async exec(cmd, timeout = 0, outputCallback = null) {
55
61
  debug("exec called", cmd);
56
62
 
57
- if (typeof cmd !== "string") {
58
- return Promise.reject(new Error("cmd must be string"));
63
+ if (typeof cmd !== "string" || cmd === "") {
64
+ return logAndReject("cmd must be string");
59
65
  }
60
- return sshExec(this.hostInfo, cmd, outputCallback);
66
+ return sshExec(this.hostInfo, cmd, timeout, outputCallback);
61
67
  }
62
68
 
63
69
 
@@ -65,11 +71,12 @@ class SshClientWrapper {
65
71
  * execute ls command and return output
66
72
  * @param {string} target - file or directory path to watch
67
73
  * @param {string[]} lsOpt - optional arguments for ls
74
+ * @param {Integer} timeout - timeout in secconds
68
75
  * @returns {string[]} - output from ls
69
76
  */
70
- async ls(target, lsOpt = []) {
77
+ async ls(target, lsOpt = [], timeout = 0) {
71
78
  debug("ls called", target, lsOpt);
72
- return ls(this.hostInfo, target, lsOpt);
79
+ return ls(this.hostInfo, target, lsOpt, timeout);
73
80
  }
74
81
 
75
82
  /**
@@ -84,7 +91,7 @@ class SshClientWrapper {
84
91
  debug("watch called", cmd, regexp, retryDelay, maxRetry);
85
92
 
86
93
  if (typeof cmd !== "string") {
87
- return Promise.reject(new Error("cmd must be string"));
94
+ return logAndReject("cmd must be string");
88
95
  }
89
96
  if (!(regexp instanceof RegExp)) {
90
97
  const err = new Error("illegal regexp specified");
@@ -92,7 +99,7 @@ class SshClientWrapper {
92
99
  err.cmd = cmd;
93
100
  err.retryDelay = retryDelay;
94
101
  err.maxRetry = maxRetry;
95
- return Promise.reject(err);
102
+ return Promise.reject(new Error("err"));
96
103
  }
97
104
  //not implemented yet !!!
98
105
  return this.exec(cmd, ()=>{
@@ -104,27 +111,30 @@ class SshClientWrapper {
104
111
  * @param {string[]} src - File or directory name which to be recieve.
105
112
  * @param {string} dst - Destination path.
106
113
  * @param {string[]} opt - option for rsync
114
+ * @param {Integer} timeout - timeout in secconds
107
115
  * @returns {Promise} - Resolved with undefined when file transfer is done.
108
116
  */
109
- async send(src, dst, opt) {
117
+ async send(src, dst, opt = [], timeout = 0) {
110
118
  debug("send called", src, dst, opt);
111
119
 
112
120
  if (!isArrayOfString(src)) {
113
- const err = "src must be array of string";
114
- debug(err);
115
- return new Error(err);
121
+ return logAndReject("src must be array of string");
122
+ }
123
+ if (!src.some((e)=>{
124
+ return e !== "";
125
+ })) {
126
+ return logAndReject("src must contain non-empty string");
116
127
  }
117
128
  if (typeof dst !== "string") {
118
- const err = "dst must be string";
119
- debug(err);
120
- return new Error(err);
129
+ return logAndReject("dst must be string");
130
+ }
131
+ if (dst === "") {
132
+ return logAndReject("dst must be non-empty string");
121
133
  }
122
134
  if (typeof opt !== "undefined" && !isArrayOfString(opt)) {
123
- const err = "opt must be array of string";
124
- debug(err);
125
- return new Error(err);
135
+ return logAndReject("opt must be array of string");
126
136
  }
127
- return send(this.hostInfo, src, dst, opt);
137
+ return send(this.hostInfo, src, dst, opt, timeout);
128
138
  }
129
139
 
130
140
  /**
@@ -132,27 +142,30 @@ class SshClientWrapper {
132
142
  * @param {string[]} src - File or directory name which to be recieve.
133
143
  * @param {string} dst - Destination path.
134
144
  * @param {string[]} opt - option for rsync
145
+ * @param {Integer} timeout - timeout in secconds
135
146
  * @returns {Promise} - Resolved with undefined when file transfer is done.
136
147
  */
137
- async recv(src, dst, opt) {
148
+ async recv(src, dst, opt = [], timeout = 0) {
138
149
  debug("recv called", src, dst, opt);
139
150
 
140
151
  if (!isArrayOfString(src)) {
141
- const err = "src must be array of string";
142
- debug(err);
143
- return new Error(err);
152
+ return logAndReject("src must be array of string");
153
+ }
154
+ if (!src.some((e)=>{
155
+ return e !== "";
156
+ })) {
157
+ return logAndReject("src must contain non-empty string");
144
158
  }
145
159
  if (typeof dst !== "string") {
146
- const err = "dst must be string";
147
- debug(err);
148
- return new Error(err);
160
+ return logAndReject("dst must be string");
161
+ }
162
+ if (dst === "") {
163
+ return logAndReject("dst must be non-empty string");
149
164
  }
150
165
  if (typeof opt !== "undefined" && !isArrayOfString(opt)) {
151
- const err = "opt must be array of string";
152
- debug(err);
153
- return new Error(err);
166
+ return logAndReject("opt must be array of string");
154
167
  }
155
- return recv(this.hostInfo, src, dst, opt);
168
+ return recv(this.hostInfo, src, dst, opt, timeout);
156
169
  }
157
170
 
158
171
  /**
@@ -165,7 +178,7 @@ class SshClientWrapper {
165
178
  debugVerbose("hostInfo=", this.hostInfo);
166
179
 
167
180
  if (!Number.isInteger(timeout) || timeout <= 0) {
168
- return new Error("timeout must be positive integer");
181
+ return logAndReject("timeout must be positive integer");
169
182
  }
170
183
  return canConnect(this.hostInfo, timeout);
171
184
  }
package/lib/rsyncExec.js CHANGED
@@ -16,10 +16,11 @@ const { sshCmd, rsyncCmd, getSshOption } = require("./util.js");
16
16
  * @param {[string]} argSrc - File or directory name which to be send.
17
17
  * @param {string} dst - Destination path.
18
18
  * @param {[string]} rsyncOpt - optional arguments for rsync
19
+ * @param {Integer} timeout - timeout in secconds
19
20
  *
20
21
  * this function execute rsync -avv --copy-unsafe-links src rsync://[user@]host[:port]/dst
21
22
  */
22
- async function send(hostInfo, argSrc, dst, rsyncOpt = []) {
23
+ async function send(hostInfo, argSrc, dst, rsyncOpt, timeout) {
23
24
  await connect(hostInfo);
24
25
  const promises = await Promise.allSettled(argSrc.map((e)=>{
25
26
  return glob(e);
@@ -65,7 +66,7 @@ async function send(hostInfo, argSrc, dst, rsyncOpt = []) {
65
66
  }
66
67
  debug(`send ${src} to ${hostInfo.host}:${dst}`);
67
68
  debugVerbose(`send: ${rsyncCmd} ${args.join(" ")}`);
68
- return retryWrapper(fork.bind(null, hostInfo, rsyncCmd, args), hostInfo);
69
+ return retryWrapper(fork.bind(null, hostInfo, rsyncCmd, args, timeout), hostInfo);
69
70
  }
70
71
 
71
72
  /**
@@ -74,10 +75,11 @@ async function send(hostInfo, argSrc, dst, rsyncOpt = []) {
74
75
  * @param {[string]} argSrc - File or directory name which to be send.
75
76
  * @param {string} dst - Destination path.
76
77
  * @param {[string]} rsyncOpt - optional arguments for rsync
78
+ * @param {Integer} timeout - timeout in secconds
77
79
  *
78
80
  * this function execute rsync -av --copy-unsafe-links rsync://[user@]host[:port]/src dst
79
81
  */
80
- async function recv(hostInfo, argSrc, dst, rsyncOpt = []) {
82
+ async function recv(hostInfo, argSrc, dst, rsyncOpt, timeout) {
81
83
  await connect(hostInfo);
82
84
  const src = `${hostInfo.host}:${argSrc.join(" ")}`;
83
85
  const sshArgs = getSshOption(hostInfo, true);
@@ -87,7 +89,7 @@ async function recv(hostInfo, argSrc, dst, rsyncOpt = []) {
87
89
 
88
90
  debug(`recv ${src} to ${dst}`);
89
91
  debugVerbose(`recv: ${rsyncCmd} ${args.join(" ")}`);
90
- return retryWrapper(fork.bind(null, hostInfo, rsyncCmd, args), hostInfo);
92
+ return retryWrapper(fork.bind(null, hostInfo, rsyncCmd, args, timeout), hostInfo);
91
93
  }
92
94
  module.exports = {
93
95
  send,
package/lib/sshExec.js CHANGED
@@ -1,20 +1,20 @@
1
1
  "use strict";
2
- const { setTimeout: setTimeoutPromise } = require("timers/promises");
3
2
  const crypto = require("crypto");
4
3
  const promiseRetry = require("promise-retry");
5
4
  const debug = require("debug")("sshClientWrapper:debug:sshExec");
6
5
  const debugVerbose = require("debug")("sshClientWrapper:verbose:sshExec");
7
6
  const debugSendMasterPty = require("debug")("sshClientWrapper:insecure:sshExec_masterPty");
8
- const { sendPty, sshCmd, getSshOption, sanityCheck } = require("./util.js");
7
+ const { sendPty, sshCmd, getSshOption, sanityCheck, watchDogTimer } = require("./util.js");
9
8
  const { fork, getMasterPty } = require("./fork.js");
10
9
 
11
10
  /**
12
11
  * Execute command on remote host.
13
12
  * @param {Object} hostInfo - hostinfo object
14
13
  * @param {string} cmd - Cmdline which will be executed on remote host.
14
+ * @param {number} timeout - timeout (in sec.)
15
15
  * @param {Function} outputCallback - Callback routine for stdout and stderr.
16
16
  */
17
- async function sshExec(hostInfo, cmd, outputCallback) {
17
+ async function sshExec(hostInfo, cmd, timeout, outputCallback) {
18
18
  await connect(hostInfo);
19
19
  debug("exec", cmd, "on remote server");
20
20
  const args = getSshOption(hostInfo, false);
@@ -22,7 +22,7 @@ async function sshExec(hostInfo, cmd, outputCallback) {
22
22
  debugVerbose(`exec: ${sshCmd} ${args.join(" ")}`);
23
23
 
24
24
  try {
25
- const rt = await retryWrapper(fork.bind(null, hostInfo, sshCmd, args, outputCallback), hostInfo);
25
+ const rt = await retryWrapper(fork.bind(null, hostInfo, sshCmd, args, timeout, outputCallback), hostInfo);
26
26
  return rt;
27
27
  } catch (e) {
28
28
  if (typeof e.rt === "undefined") {
@@ -64,11 +64,8 @@ async function canConnect(hostInfo, timeout = 60) {
64
64
  * @param {Object} hostInfo - hostinfo object
65
65
  * @returns {boolean} - true if master session exists
66
66
  */
67
- async function existsMaster(hostInfo) {
67
+ async function existsMaster(hostInfo, argTimeout) {
68
68
  debugVerbose("existsMaster called");
69
- const ac = new AbortController();
70
- const signal = ac.signal;
71
-
72
69
  const args = getSshOption(hostInfo);
73
70
  args.push("-Ocheck");
74
71
 
@@ -77,40 +74,22 @@ async function existsMaster(hostInfo) {
77
74
  output += e;
78
75
  };
79
76
 
80
- let rt = true; //if timeout occurred during ssh -Ocheck, it will turn to false
81
- let p;
82
- const timeout = hostInfo.ConnectTimeout || 60;
83
- setTimeoutPromise(timeout * 1000, "neverUsedValue", { signal })
84
- .then(()=>{
85
- debug(`can not finish master session check within ${timeout} sec`);
86
-
87
- if (p.pty) {
88
- p.pty.kill();
89
- }
90
- rt = false;
91
- })
92
- .catch((err)=>{
93
- if (err.name === "AbortError") {
94
- debug("timeout was aborted because master session check has done");
95
- } else {
96
- throw err;
97
- }
98
- });
77
+ const timeout = argTimeout || hostInfo.ConnectTimeout || 60;
78
+ debug(`check master session: ${sshCmd} ${args.join(" ")}`);
99
79
 
100
80
  try {
101
- debug(`check master session: ${sshCmd} ${args.join(" ")}`);
102
- p = fork(hostInfo, sshCmd, args, cb);
103
- await p;
81
+ await fork(hostInfo, sshCmd, args, timeout, cb);
104
82
  } catch (err) {
83
+ if (err.code === "TIMEEXPIRE") {
84
+ return false;
85
+ }
105
86
  if (err.rt !== 255) {
106
87
  debug("check master session failed", err);
107
88
  throw err;
108
89
  }
109
- } finally {
110
- ac.abort();
111
90
  }
112
91
 
113
- return rt && !/Control socket connect.*: No such file or directory/.test(output);
92
+ return !/Control socket connect.*: No such file or directory/.test(output);
114
93
  }
115
94
 
116
95
  /**
@@ -118,7 +97,7 @@ async function existsMaster(hostInfo) {
118
97
  * @param {Object} hostInfo - hostinfo object
119
98
  */
120
99
  async function connect(hostInfo, timeout = 60) {
121
- if (await existsMaster(hostInfo)) {
100
+ if (await existsMaster(hostInfo, timeout)) {
122
101
  debugVerbose("master connection exists");
123
102
  return;
124
103
  }
@@ -127,11 +106,13 @@ async function connect(hostInfo, timeout = 60) {
127
106
  getMasterPty(hostInfo, debugSendMasterPty);
128
107
 
129
108
  debug(`create master ssh session to ${hostInfo.host}`);
130
- const ac = new AbortController();
131
- const signal = ac.signal;
132
- setTimeoutPromise(timeout * 1000, "neverUsedValue", { signal })
133
- .then(()=>{
134
- debug(`can not connect within ${timeout} sec`);
109
+
110
+ const words = crypto.randomUUID();
111
+ const re = new RegExp(`^${words}`);
112
+
113
+ let ac = null;
114
+ const p = new Promise((resolve, reject)=>{
115
+ ac = watchDogTimer(timeout, "make connection", ()=>{
135
116
  sendPty(hostInfo.masterPty, "\n", debugSendMasterPty, 137);
136
117
  sendPty(hostInfo.masterPty, "exit\n", debugSendMasterPty, 138);
137
118
 
@@ -139,19 +120,9 @@ async function connect(hostInfo, timeout = 60) {
139
120
  err.host = hostInfo.host;
140
121
  err.user = hostInfo.user;
141
122
  err.port = hostInfo.port;
142
- throw err;
143
- })
144
- .catch((err)=>{
145
- if (err.name === "AbortError") {
146
- debug("timeout was aborted because connection check has done");
147
- } else {
148
- throw err;
149
- }
123
+ reject(err);
150
124
  });
151
125
 
152
- const words = crypto.randomUUID();
153
- const re = new RegExp(`^${words}`);
154
- const p = new Promise((resolve, reject)=>{
155
126
  let done = false;
156
127
  hostInfo.masterPty.onData((data)=>{
157
128
  if (done) {
@@ -190,7 +161,9 @@ async function connect(hostInfo, timeout = 60) {
190
161
  await p;
191
162
  debug(`connected to ${hostInfo.host}`);
192
163
  } finally {
193
- ac.abort();
164
+ if (ac !== null) {
165
+ ac.abort();
166
+ }
194
167
  }
195
168
  }
196
169
 
@@ -209,7 +182,7 @@ async function disconnect(hostInfo) {
209
182
  };
210
183
  try {
211
184
  debug(`disconnect: ${sshCmd} ${args.join(" ")}`);
212
- await fork(hostInfo, sshCmd, args, cb);
185
+ await fork(hostInfo, sshCmd, args, 0, cb);
213
186
  } catch (err) {
214
187
  if (err.rt === 255) {
215
188
  return;
@@ -236,12 +209,13 @@ async function disconnect(hostInfo) {
236
209
  * @param {Object} hostInfo - hostinfo object
237
210
  * @param {string} target - file or directory path to watch
238
211
  * @param {string[]} lsOpt - optional arguments for ls
212
+ * @param {number} timeout - timeout (in sec.)
239
213
  * @returns {string[]} - output from ls
240
214
  */
241
- async function ls(hostInfo, target, lsOpt = []) {
215
+ async function ls(hostInfo, target, lsOpt, timeout) {
242
216
  const outputArray = [];
243
217
  try {
244
- await sshExec(hostInfo, `ls ${lsOpt.join(" ")} ${target}`, (data)=>{
218
+ await sshExec(hostInfo, `ls ${lsOpt.join(" ")} ${target}`, timeout, (data)=>{
245
219
  outputArray.push(data);
246
220
  });
247
221
  } catch (e) {
package/lib/util.js CHANGED
@@ -2,6 +2,7 @@
2
2
  const { EOL } = require("os");
3
3
  const fs = require("fs");
4
4
  const debug = require("debug")("sshClientWrapper:debug:util");
5
+ const { setTimeout: setTimeoutPromise } = require("timers/promises");
5
6
 
6
7
  const sshCmd = "ssh";
7
8
  const rsyncCmd = "rsync";
@@ -122,6 +123,31 @@ const sendPty = async (pty, message, logger, label)=>{
122
123
  pty.write(message);
123
124
  };
124
125
 
126
+ /**
127
+ * set watch dog timer
128
+ * @param {number} timeout - timeout (in sec.)
129
+ * @param {string} label - used in both timeout and aborted message
130
+ * @param {Function} cb - call back function which will be called after timeout
131
+ * @returns {AbortController} - ac to stop watch dog timer
132
+ */
133
+ const watchDogTimer = (timeout, label, cb)=>{
134
+ const ac = new AbortController();
135
+ const signal = ac.signal;
136
+ setTimeoutPromise(timeout * 1000, "neverUsedValue", { signal })
137
+ .then(()=>{
138
+ debug(`can not finish ${label} within ${timeout} sec`);
139
+ return typeof cb === "function" ? cb() : true;
140
+ })
141
+ .catch((err)=>{
142
+ if (err.name === "AbortError") {
143
+ debug(`timeout was aborted because ${label} has done`);
144
+ } else {
145
+ throw err;
146
+ }
147
+ });
148
+ return ac;
149
+ };
150
+
125
151
  module.exports = {
126
152
  sshCmd,
127
153
  rsyncCmd,
@@ -132,5 +158,6 @@ module.exports = {
132
158
  getSshOption,
133
159
  sanityCheck,
134
160
  isArrayOfString,
135
- sendPty
161
+ sendPty,
162
+ watchDogTimer
136
163
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ssh-client-wrapper",
3
- "version": "1.8.3",
3
+ "version": "2.0.2",
4
4
  "description": "wrapper library for open ssh client and rsync",
5
5
  "keywords": [
6
6
  "ssh",
@@ -38,7 +38,8 @@
38
38
  "glob": "^10.3.3",
39
39
  "node-pty": "^0.10.1",
40
40
  "nyc": "^15.1.0",
41
- "promise-retry": "^2.0.1"
41
+ "promise-retry": "^2.0.1",
42
+ "rewire": "^7.0.0"
42
43
  },
43
44
  "devDependencies": {
44
45
  "chai": "^4.3.7",
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ process.on("unhandledRejection", console.dir); //eslint-disable-line no-console
3
+ Error.traceLimit = 100000;
4
+
5
+ //setup test framework
6
+ const chai = require("chai");
7
+ const { expect } = require("chai");
8
+ const sinon = require("sinon");
9
+ chai.use(require("sinon-chai"));
10
+ chai.use(require("chai-as-promised"));
11
+
12
+ const rewire = require("rewire");
13
+
14
+ //testee
15
+ const SshClientWrapper = rewire("../lib/index.js");
16
+
17
+ const sshExec = sinon.stub();
18
+ const canConnect = sinon.stub();
19
+ const disconnect = sinon.stub();
20
+ const ls = sinon.stub();
21
+ const send = sinon.stub();
22
+ const recv = sinon.stub();
23
+
24
+ //eslint-disable-next-line no-underscore-dangle
25
+ SshClientWrapper.__set__({
26
+ sshExec, canConnect, disconnect, ls, send, recv
27
+ });
28
+
29
+
30
+ describe("test for interface", ()=>{
31
+ describe("test for constructor", ()=>{
32
+ it("should throw error when instanciate with non-empty string host", ()=>{
33
+ expect(()=>{
34
+ //eslint-disable-next-line no-new
35
+ new SshClientWrapper({});
36
+ }).to.throw("host must be non-empty string");
37
+ expect(()=>{
38
+ //eslint-disable-next-line no-new
39
+ new SshClientWrapper({ host: 1 });
40
+ }).to.throw("host must be non-empty string");
41
+ expect(()=>{
42
+ //eslint-disable-next-line no-new
43
+ new SshClientWrapper({ host: "" });
44
+ }).to.throw("host must be non-empty string");
45
+ });
46
+ it("should successfully instanciate only with host prop", ()=>{
47
+ expect(new SshClientWrapper({ host: "hoge" })).to.have.property("exec");
48
+ expect(new SshClientWrapper({ host: "hoge" })).to.have.property("ls");
49
+ expect(new SshClientWrapper({ host: "hoge" })).to.have.property("watch");
50
+ expect(new SshClientWrapper({ host: "hoge" })).to.have.property("send");
51
+ expect(new SshClientWrapper({ host: "hoge" })).to.have.property("recv");
52
+ expect(new SshClientWrapper({ host: "hoge" })).to.have.property("canConnect");
53
+ expect(new SshClientWrapper({ host: "hoge" })).to.have.property("disconnect");
54
+ });
55
+ });
56
+ describe("test for public method", ()=>{
57
+ let ssh;
58
+ const hostInfoOrg = { host: "hoge" };
59
+ const hostInfo = { ...hostInfoOrg, masterPty: null };
60
+ beforeEach(()=>{
61
+ ssh = new SshClientWrapper(hostInfoOrg);
62
+ sshExec.reset();
63
+ canConnect.reset();
64
+ disconnect.reset();
65
+ ls.reset();
66
+ send.reset();
67
+ recv.reset();
68
+ });
69
+ describe("test for exec", ()=>{
70
+ it("should call sshExec with cmd", ()=>{
71
+ ssh.exec("ls");
72
+ expect(sshExec).to.be.calledWith(hostInfo, "ls", 0, null);
73
+ });
74
+ it("should call sshExec with cmd and timeout", ()=>{
75
+ ssh.exec("ls", 10);
76
+ expect(sshExec).to.be.calledWith(hostInfo, "ls", 10, null);
77
+ });
78
+ it("should call sshExec with cmd, timeout and call back function", ()=>{
79
+ const cb = ()=>{};
80
+ ssh.exec("ls", 10, cb);
81
+ expect(sshExec).to.be.calledWith(hostInfo, "ls", 10, cb);
82
+ });
83
+ it("should reject if cmd is not string (number)", ()=>{
84
+ return expect(ssh.exec(0)).to.be.rejectedWith("cmd must be string");
85
+ });
86
+ it("should reject if cmd is not string (null)", ()=>{
87
+ return expect(ssh.exec(null)).to.be.rejectedWith("cmd must be string");
88
+ });
89
+ it("should reject if cmd is empty string", ()=>{
90
+ return expect(ssh.exec("")).to.be.rejectedWith("cmd must be string");
91
+ });
92
+ });
93
+ describe("test for ls", ()=>{
94
+ it("should call ls without timeout", ()=>{
95
+ ssh.ls("hoge");
96
+ expect(ls).to.be.calledWith(hostInfo, "hoge", [], 0);
97
+ });
98
+ it("should call ls with option", ()=>{
99
+ ssh.ls("hoge", ["-l", "-r", "-t"]);
100
+ expect(ls).to.be.calledWith(hostInfo, "hoge", ["-l", "-r", "-t"], 0);
101
+ });
102
+ it("should call ls with option and timeout", ()=>{
103
+ ssh.ls("hoge", ["-l", "-r", "-t"], 10);
104
+ expect(ls).to.be.calledWith(hostInfo, "hoge", ["-l", "-r", "-t"], 10);
105
+ });
106
+ });
107
+ describe("test for send", ()=>{
108
+ it("should call send with src and dst", ()=>{
109
+ ssh.send(["src"], "dst");
110
+ expect(send).to.be.calledWith(hostInfo, ["src"], "dst", [], 0);
111
+ });
112
+ it("should call send with src, dst, and opt", ()=>{
113
+ ssh.send(["src"], "dst", ["--dry-run", "--delete"]);
114
+ expect(send).to.be.calledWith(hostInfo, ["src"], "dst", ["--dry-run", "--delete"], 0);
115
+ });
116
+ it("should call send with src, dst, opt, and timeout", ()=>{
117
+ ssh.send(["src"], "dst", ["--dry-run", "--delete"], 10);
118
+ expect(send).to.be.calledWith(hostInfo, ["src"], "dst", ["--dry-run", "--delete"], 10);
119
+ });
120
+ it("should be rejected if src is not array of string", ()=>{
121
+ return expect(ssh.send(1)).to.be.rejectedWith("src must be array of string");
122
+ });
123
+ it("should be rejected if src has only empty string element", ()=>{
124
+ return expect(ssh.send(["", ""])).to.be.rejectedWith("src must contain non-empty string");
125
+ });
126
+ it("should be rejected if src has only empty string element", ()=>{
127
+ return expect(ssh.send([""])).to.be.rejectedWith("src must contain non-empty string");
128
+ });
129
+ it("should be called if src includes empty string ", ()=>{
130
+ ssh.send(["src", ""], "dst");
131
+ expect(send).to.be.calledWith(hostInfo, ["src", ""], "dst", [], 0);
132
+ });
133
+ it("should be rejected if dst is not string", ()=>{
134
+ return expect(ssh.send(["src"], 0)).to.be.rejectedWith("dst must be string");
135
+ });
136
+ it("should be rejected if dst is empty string", ()=>{
137
+ return expect(ssh.send(["src"], "")).to.be.rejectedWith("dst must be non-empty string");
138
+ });
139
+ it("should be rejected if opt is not array of string", ()=>{
140
+ return expect(ssh.send(["src"], "dst", [null])).to.be.rejectedWith("opt must be array of string");
141
+ });
142
+ });
143
+ describe("test for recv", ()=>{
144
+ it("should call recv with src and dst", ()=>{
145
+ ssh.recv(["src"], "dst");
146
+ expect(recv).to.be.calledWith(hostInfo, ["src"], "dst", [], 0);
147
+ });
148
+ it("should call recv with src, dst, and opt", ()=>{
149
+ ssh.recv(["src"], "dst", ["--dry-run", "--delete"]);
150
+ expect(recv).to.be.calledWith(hostInfo, ["src"], "dst", ["--dry-run", "--delete"], 0);
151
+ });
152
+ it("should call recv with src, dst, opt, and timeout", ()=>{
153
+ ssh.recv(["src"], "dst", ["--dry-run", "--delete"], 10);
154
+ expect(recv).to.be.calledWith(hostInfo, ["src"], "dst", ["--dry-run", "--delete"], 10);
155
+ });
156
+ it("should be rejected if src is not array of string", ()=>{
157
+ return expect(ssh.recv(1)).to.be.rejectedWith("src must be array of string");
158
+ });
159
+ it("should be rejected if src has only empty string element", ()=>{
160
+ return expect(ssh.recv(["", ""])).to.be.rejectedWith("src must contain non-empty string");
161
+ });
162
+ it("should be rejected if src has only empty string element", ()=>{
163
+ return expect(ssh.recv([""])).to.be.rejectedWith("src must contain non-empty string");
164
+ });
165
+ it("should be called if src includes empty string ", ()=>{
166
+ ssh.recv(["src", ""], "dst");
167
+ expect(recv).to.be.calledWith(hostInfo, ["src", ""], "dst", [], 0);
168
+ });
169
+ it("should be rejected if dst is not string", ()=>{
170
+ return expect(ssh.recv(["src"], 0)).to.be.rejectedWith("dst must be string");
171
+ });
172
+ it("should be rejected if dst is empty string", ()=>{
173
+ return expect(ssh.recv(["src"], "")).to.be.rejectedWith("dst must be non-empty string");
174
+ });
175
+ it("should be rejected if opt is not array of string", ()=>{
176
+ return expect(ssh.recv(["src"], "dst", [null])).to.be.rejectedWith("opt must be array of string");
177
+ });
178
+ });
179
+ });
180
+ });
package/test/rsyncExec.js CHANGED
@@ -50,7 +50,7 @@ describe("test rsync exec", function() {
50
50
  };
51
51
  let remoteHome;
52
52
  before(async ()=>{
53
- await sshExec(hostInfo, "pwd", (data)=>{
53
+ await sshExec(hostInfo, "pwd", 0, (data)=>{
54
54
  remoteHome = data.trim();
55
55
  });
56
56
  });
@@ -71,57 +71,57 @@ describe("test rsync exec", function() {
71
71
  describe("#send", async ()=>{
72
72
  describe("send single file", ()=>{
73
73
  it("should accept relative src file and relative dst dir name", async ()=>{
74
- await send(hostInfo, [localFiles[0]], remoteEmptyDir);
74
+ await send(hostInfo, [localFiles[0]], remoteEmptyDir, [], 0);
75
75
 
76
- await sshExec(hostInfo, `ls ${remoteEmptyDir}`, output);
76
+ await sshExec(hostInfo, `ls ${remoteEmptyDir}`, 0, output);
77
77
  expect(formatLsOutput(rt)).to.have.members(["foo"]);
78
78
  });
79
79
  it("should accept absolute src file and relative dst dir name", async ()=>{
80
- await send(hostInfo, [path.resolve(localFiles[3])], remoteEmptyDir);
80
+ await send(hostInfo, [path.resolve(localFiles[3])], remoteEmptyDir, [], 0);
81
81
 
82
- await sshExec(hostInfo, `ls ${remoteEmptyDir}`, output);
82
+ await sshExec(hostInfo, `ls ${remoteEmptyDir}`, 0, output);
83
83
  expect(formatLsOutput(rt)).to.have.members(["piyo"]);
84
84
  });
85
85
  it("should accept relative src file and absolute dst dir name", async ()=>{
86
- await send(hostInfo, [localFiles[0]], `${path.posix.join(remoteHome, remoteEmptyDir)}/`);
86
+ await send(hostInfo, [localFiles[0]], `${path.posix.join(remoteHome, remoteEmptyDir)}/`, [], 0);
87
87
 
88
- await sshExec(hostInfo, `ls ${remoteEmptyDir}`, output);
88
+ await sshExec(hostInfo, `ls ${remoteEmptyDir}`, 0, output);
89
89
  expect(formatLsOutput(rt)).to.have.members(["foo"]);
90
90
  });
91
91
  it("should accept absolute src file and absolute dst dir name", async ()=>{
92
- await send(hostInfo, [path.resolve(localFiles[0])], path.posix.join(remoteHome, remoteEmptyDir));
92
+ await send(hostInfo, [path.resolve(localFiles[0])], path.posix.join(remoteHome, remoteEmptyDir), [], 0);
93
93
 
94
- await sshExec(hostInfo, `ls ${remoteEmptyDir}`, output);
94
+ await sshExec(hostInfo, `ls ${remoteEmptyDir}`, 0, output);
95
95
  expect(formatLsOutput(rt)).to.have.members(["foo"]);
96
96
  });
97
97
  it("should accept relative src file and relative dst file name", async ()=>{
98
- await send(hostInfo, [localFiles[0]], path.posix.join(remoteEmptyDir, "hoge"));
98
+ await send(hostInfo, [localFiles[0]], path.posix.join(remoteEmptyDir, "hoge"), [], 0);
99
99
 
100
- await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, "hoge")}`, output);
100
+ await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, "hoge")}`, 0, output);
101
101
  expect(formatLsOutput(rt.map((e)=>{
102
102
  return path.posix.basename(e);
103
103
  }))).to.have.members(["hoge"]);
104
104
  });
105
105
  it("should accept absolute src file and relative dst file name", async ()=>{
106
- await send(hostInfo, [path.resolve(localFiles[0])], path.posix.join(remoteEmptyDir, "hoge"));
106
+ await send(hostInfo, [path.resolve(localFiles[0])], path.posix.join(remoteEmptyDir, "hoge"), [], 0);
107
107
 
108
- await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, "hoge")}`, output);
108
+ await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, "hoge")}`, 0, output);
109
109
  expect(formatLsOutput(rt.map((e)=>{
110
110
  return path.posix.basename(e);
111
111
  }))).to.have.members(["hoge"]);
112
112
  });
113
113
  it("should accept relative src file and absolute dst file name", async ()=>{
114
- await send(hostInfo, [localFiles[0]], path.posix.join(remoteHome, remoteEmptyDir, "hoge"));
114
+ await send(hostInfo, [localFiles[0]], path.posix.join(remoteHome, remoteEmptyDir, "hoge"), [], 0);
115
115
 
116
- await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, "hoge")}`, output);
116
+ await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, "hoge")}`, 0, output);
117
117
  expect(formatLsOutput(rt.map((e)=>{
118
118
  return path.posix.basename(e);
119
119
  }))).to.have.members(["hoge"]);
120
120
  });
121
121
  it("should accept absolute src file and absolute dst file name", async ()=>{
122
- await send(hostInfo, [path.resolve(localFiles[0])], path.posix.join(remoteHome, remoteEmptyDir, "hoge"));
122
+ await send(hostInfo, [path.resolve(localFiles[0])], path.posix.join(remoteHome, remoteEmptyDir, "hoge"), [], 0);
123
123
 
124
- await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, "hoge")}`, output);
124
+ await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, "hoge")}`, 0, output);
125
125
  expect(formatLsOutput(rt.map((e)=>{
126
126
  return path.posix.basename(e);
127
127
  }))).to.have.members(["hoge"]);
@@ -129,8 +129,8 @@ describe("test rsync exec", function() {
129
129
 
130
130
  it("should overwrite existing file", async ()=>{
131
131
  const target = path.posix.join(remoteEmptyDir, "hoge");
132
- await send(hostInfo, [localFiles[0]], target);
133
- await sshExec(hostInfo, `ls ${target}`, output);
132
+ await send(hostInfo, [localFiles[0]], target, [], 0);
133
+ await sshExec(hostInfo, `ls ${target}`, 0, output);
134
134
  expect(formatLsOutput(rt.map((e)=>{
135
135
  return path.posix.basename(e);
136
136
  }))).to.have.members(["hoge"]);
@@ -139,168 +139,168 @@ describe("test rsync exec", function() {
139
139
  const time = new Date();
140
140
  time.setMinutes(time.getMinutes() + 3);
141
141
  await fs.utimes(localFiles[1], time, time);
142
- await send(hostInfo, [localFiles[1]], target);
143
- await sshExec(hostInfo, `ls ${target}`, output);
142
+ await send(hostInfo, [localFiles[1]], target, [], 0);
143
+ await sshExec(hostInfo, `ls ${target}`, 0, output);
144
144
  expect(formatLsOutput(rt.map((e)=>{
145
145
  return path.posix.basename(e);
146
146
  }))).to.have.members(["hoge"]);
147
147
 
148
148
  rt.splice(0, rt.length);
149
- await sshExec(hostInfo, `cat ${target}`, output);
149
+ await sshExec(hostInfo, `cat ${target}`, 0, output);
150
150
  expect(formatLsOutput(rt)).to.have.members(["ARssh_testLocalDir/bar"]);
151
151
  });
152
152
  });
153
153
  describe("send directory tree", ()=>{
154
154
  it("should accept relative src dirname and relative dst dirname", async ()=>{
155
- await send(hostInfo, [localRoot], remoteEmptyDir);
155
+ await send(hostInfo, [localRoot], remoteEmptyDir, [], 0);
156
156
 
157
- await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot)}`, output);
157
+ await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot)}`, 0, output);
158
158
  expect(formatLsOutput(rt)).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
159
159
 
160
160
  rt.splice(0, rt.length);
161
- await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot, "hoge")}`, output);
161
+ await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot, "hoge")}`, 0, output);
162
162
  expect(formatLsOutput(rt)).to.have.members(["piyo", "puyo", "poyo"]);
163
163
  });
164
164
  it("should accept absolute src dirname and relative dst dirname", async ()=>{
165
- await send(hostInfo, [path.resolve(localRoot)], remoteEmptyDir);
165
+ await send(hostInfo, [path.resolve(localRoot)], remoteEmptyDir, [], 0);
166
166
 
167
- await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot)}`, output);
167
+ await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot)}`, 0, output);
168
168
  expect(formatLsOutput(rt)).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
169
169
 
170
170
  rt.splice(0, rt.length);
171
- await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot, "hoge")}`, output);
171
+ await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot, "hoge")}`, 0, output);
172
172
  expect(formatLsOutput(rt)).to.have.members(["piyo", "puyo", "poyo"]);
173
173
  });
174
174
  it("should accept relative src dirname and absolute dst dirname", async ()=>{
175
- await send(hostInfo, [localRoot], path.posix.join(remoteHome, remoteEmptyDir));
175
+ await send(hostInfo, [localRoot], path.posix.join(remoteHome, remoteEmptyDir), [], 0);
176
176
 
177
- await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot)}`, output);
177
+ await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot)}`, 0, output);
178
178
  expect(formatLsOutput(rt)).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
179
179
 
180
180
  rt.splice(0, rt.length);
181
- await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot, "hoge")}`, output);
181
+ await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot, "hoge")}`, 0, output);
182
182
  expect(formatLsOutput(rt)).to.have.members(["piyo", "puyo", "poyo"]);
183
183
  });
184
184
  it("should accept absolute src dirname and absolute dst dirname", async ()=>{
185
- await send(hostInfo, [path.resolve(localRoot)], path.posix.join(remoteHome, remoteEmptyDir));
185
+ await send(hostInfo, [path.resolve(localRoot)], path.posix.join(remoteHome, remoteEmptyDir), [], 0);
186
186
 
187
- await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot)}`, output);
187
+ await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot)}`, 0, output);
188
188
  expect(formatLsOutput(rt)).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
189
189
 
190
190
  rt.splice(0, rt.length);
191
- await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot, "hoge")}`, output);
191
+ await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot, "hoge")}`, 0, output);
192
192
  expect(formatLsOutput(rt)).to.have.members(["piyo", "puyo", "poyo"]);
193
193
  });
194
194
  //only option need special treatment in rsyncExec. it's hard to set in WHEEL
195
195
  it.skip("[not implemented] should send directory tree if only filter matched", async ()=>{
196
- await send(hostInfo, [localRoot], remoteEmptyDir, [`--include=${localRoot}`, `--include=${localRoot}/ba*`, `--include=${localRoot}/hoge/*`, "--exclude=*"]);
196
+ await send(hostInfo, [localRoot], remoteEmptyDir, [`--include=${localRoot}`, `--include=${localRoot}/ba*`, `--include=${localRoot}/hoge/*`, "--exclude=*"], [], 0);
197
197
 
198
- await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot)}`, output);
198
+ await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot)}`, 0, output);
199
199
  expect(formatLsOutput(rt)).to.have.members(["hoge", "bar", "baz"]);
200
200
 
201
201
  rt.splice(0, rt.length);
202
- await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot, "hoge")}`, output);
202
+ await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot, "hoge")}`, 0, output);
203
203
  expect(formatLsOutput(rt)).to.have.members(["piyo", "puyo", "poyo"]);
204
204
  });
205
205
  it("should send directory tree if exclude filter not matched", async ()=>{
206
- await send(hostInfo, [localRoot], remoteEmptyDir, ["--exclude=ba*", "--exclude=hoge*}"]);
206
+ await send(hostInfo, [localRoot], remoteEmptyDir, ["--exclude=ba*", "--exclude=hoge*}"], [], 0);
207
207
 
208
- await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot)}`, output);
208
+ await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot)}`, 0, output);
209
209
  expect(formatLsOutput(rt)).to.have.members(["foo", "hoge", "huga"]);
210
210
  });
211
211
  it.skip("[not implemented] should send directory tree if only filter matched but exclude filter not matched", async ()=>{
212
- await send(hostInfo, [localRoot], remoteEmptyDir, "*/{ba*,hoge/*}", "**/poyo");
212
+ await send(hostInfo, [localRoot], remoteEmptyDir, "*/{ba*,hoge/*}", "**/poyo", [], 0);
213
213
 
214
- await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot)}`, output);
214
+ await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot)}`, 0, output);
215
215
  expect(formatLsOutput(rt)).to.have.members(["hoge", "bar", "baz"]);
216
216
 
217
217
  rt.splice(0, rt.length);
218
- await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot, "hoge")}`, output);
218
+ await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot, "hoge")}`, 0, output);
219
219
  expect(formatLsOutput(rt)).to.have.members(["piyo", "puyo"]);
220
220
  });
221
221
  it("shoud not send empty directory with -m option", async ()=>{
222
- await send(hostInfo, [localEmptyDir], remoteEmptyDir, ["-m"]);
222
+ await send(hostInfo, [localEmptyDir], remoteEmptyDir, ["-m"], [], 0);
223
223
 
224
- await sshExec(hostInfo, `ls ${remoteEmptyDir}`, output);
224
+ await sshExec(hostInfo, `ls ${remoteEmptyDir}`, 0, output);
225
225
  expect(rt).to.have.lengthOf(0);
226
226
  });
227
227
  it("should send files which match glob pattern", async ()=>{
228
- await send(hostInfo, [path.join(localRoot, "b*")], remoteEmptyDir);
228
+ await send(hostInfo, [path.join(localRoot, "b*")], remoteEmptyDir, [], 0);
229
229
 
230
- await sshExec(hostInfo, `ls ${remoteEmptyDir}`, output);
230
+ await sshExec(hostInfo, `ls ${remoteEmptyDir}`, 0, output);
231
231
  expect(formatLsOutput(rt)).to.have.members(["bar", "baz"]);
232
232
  });
233
233
  });
234
234
  describe("error case", ()=>{
235
235
  it("should not send directory to existing file path", ()=>{
236
- return expect(send(hostInfo, [localRoot], path.posix.join(remoteRoot, "foo"))).to.be.rejected;
236
+ return expect(send(hostInfo, [localRoot], path.posix.join(remoteRoot, "foo"), [], 0)).to.be.rejected;
237
237
  });
238
238
  });
239
239
  });
240
240
  describe("#recv", async ()=>{
241
241
  describe("recieve single file", ()=>{
242
242
  it("should accept relative src file and relative dst dir name", async ()=>{
243
- await recv(hostInfo, [remoteFiles[3]], localEmptyDir);
243
+ await recv(hostInfo, [remoteFiles[3]], localEmptyDir, [], 0);
244
244
 
245
245
  const rt2 = await fs.readdir(localEmptyDir);
246
246
  expect(rt2).to.have.members(["piyo"]);
247
247
  });
248
248
  it("should accept absolute src file and relative dst dir name", async ()=>{
249
- await recv(hostInfo, [path.posix.join(remoteHome, remoteFiles[3])], localEmptyDir);
249
+ await recv(hostInfo, [path.posix.join(remoteHome, remoteFiles[3])], localEmptyDir, [], 0);
250
250
 
251
251
  const rt2 = await fs.readdir(localEmptyDir);
252
252
  expect(rt2).to.have.members(["piyo"]);
253
253
  });
254
254
  it("should accept relative src file and absolute dst dir name", async ()=>{
255
- await recv(hostInfo, [remoteFiles[3]], path.resolve(localEmptyDir));
255
+ await recv(hostInfo, [remoteFiles[3]], path.resolve(localEmptyDir), [], 0);
256
256
 
257
257
  const rt2 = await fs.readdir(localEmptyDir);
258
258
  expect(rt2).to.have.members(["piyo"]);
259
259
  });
260
260
  it("should accept absolute src file and absolute dst dir name", async ()=>{
261
- await recv(hostInfo, [path.posix.join(remoteHome, remoteFiles[3])], path.resolve(localEmptyDir));
261
+ await recv(hostInfo, [path.posix.join(remoteHome, remoteFiles[3])], path.resolve(localEmptyDir), [], 0);
262
262
 
263
263
  const rt2 = await fs.readdir(localEmptyDir);
264
264
  expect(rt2).to.have.members(["piyo"]);
265
265
  });
266
266
  it("should accept relative src file and relative dst file name", async ()=>{
267
- await recv(hostInfo, [remoteFiles[0]], path.join(localEmptyDir, "hoge"));
267
+ await recv(hostInfo, [remoteFiles[0]], path.join(localEmptyDir, "hoge"), [], 0);
268
268
 
269
269
  const rt2 = await fs.readdir(localEmptyDir);
270
270
  expect(rt2).to.have.members(["hoge"]);
271
271
  });
272
272
  it("should accept absolute src file and relative dst file name", async ()=>{
273
- await recv(hostInfo, [path.posix.join(remoteHome, remoteFiles[0])], path.join(localEmptyDir, "hoge"));
273
+ await recv(hostInfo, [path.posix.join(remoteHome, remoteFiles[0])], path.join(localEmptyDir, "hoge"), [], 0);
274
274
 
275
275
  const rt2 = await fs.readdir(localEmptyDir);
276
276
  expect(rt2).to.have.members(["hoge"]);
277
277
  });
278
278
  it("should accept relative src file and absolute dst file name", async ()=>{
279
- await recv(hostInfo, [remoteFiles[0]], path.resolve(localEmptyDir, "hoge"));
279
+ await recv(hostInfo, [remoteFiles[0]], path.resolve(localEmptyDir, "hoge"), [], 0);
280
280
 
281
281
  const rt2 = await fs.readdir(localEmptyDir);
282
282
  expect(rt2).to.have.members(["hoge"]);
283
283
  });
284
284
  it("should accept absolute src file and absolute dst file name", async ()=>{
285
- await recv(hostInfo, [path.posix.join(remoteHome, remoteFiles[0])], path.resolve(localEmptyDir, "hoge"));
285
+ await recv(hostInfo, [path.posix.join(remoteHome, remoteFiles[0])], path.resolve(localEmptyDir, "hoge"), [], 0);
286
286
 
287
287
  const rt2 = await fs.readdir(localEmptyDir);
288
288
  expect(rt2).to.have.members(["hoge"]);
289
289
  });
290
290
  it("should accept glob pattern as src", async ()=>{
291
- await recv(hostInfo, [path.posix.join(remoteRoot, "b*")], path.resolve(localEmptyDir));
291
+ await recv(hostInfo, [path.posix.join(remoteRoot, "b*")], path.resolve(localEmptyDir), [], 0);
292
292
 
293
293
  const rt2 = await fs.readdir(localEmptyDir);
294
294
  expect(rt2).to.have.members(["bar", "baz"]);
295
295
  });
296
296
  it("should accept glob pattern as src", async ()=>{
297
- await recv(hostInfo, [path.posix.join(remoteRoot, "*/p[iu]yo")], path.resolve(localEmptyDir));
297
+ await recv(hostInfo, [path.posix.join(remoteRoot, "*/p[iu]yo")], path.resolve(localEmptyDir), [], 0);
298
298
 
299
299
  const rt2 = await fs.readdir(localEmptyDir);
300
300
  expect(rt2).to.have.members(["piyo", "puyo"]);
301
301
  });
302
302
  it.skip("[temporarily disabled number 23 was thrown] should accept glob pattern as src", async ()=>{
303
- await recv(hostInfo, [path.posix.join(remoteRoot, "hoge/p[iu]yo"), path.posix.join(remoteRoot, "foo")], path.resolve(localEmptyDir));
303
+ await recv(hostInfo, [path.posix.join(remoteRoot, "hoge/p[iu]yo"), path.posix.join(remoteRoot, "foo")], path.resolve(localEmptyDir), [], 0);
304
304
 
305
305
  const rt2 = await fs.readdir(localEmptyDir);
306
306
  expect(rt2).to.have.members(["piyo", "puyo", "foo"]);
@@ -308,7 +308,7 @@ describe("test rsync exec", function() {
308
308
  });
309
309
  describe("recieve directory tree", ()=>{
310
310
  it("should get directory tree which match specified glob", async ()=>{
311
- await recv(hostInfo, [path.posix.join(remoteRoot, "?o*")], localEmptyDir);
311
+ await recv(hostInfo, [path.posix.join(remoteRoot, "?o*")], localEmptyDir, [], 0);
312
312
 
313
313
  let rt2 = await fs.readdir(localEmptyDir);
314
314
  expect(rt2).to.have.members(["foo", "hoge"]);
@@ -316,7 +316,7 @@ describe("test rsync exec", function() {
316
316
  expect(rt2).to.have.members(["piyo", "puyo", "poyo"]);
317
317
  });
318
318
  it("should accept relative src dirname and relative dst dirname", async ()=>{
319
- await recv(hostInfo, [remoteRoot], localEmptyDir);
319
+ await recv(hostInfo, [remoteRoot], localEmptyDir, [], 0);
320
320
 
321
321
  let rt2 = await fs.readdir(path.posix.join(localEmptyDir, remoteRoot));
322
322
  expect(rt2).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
@@ -324,7 +324,7 @@ describe("test rsync exec", function() {
324
324
  expect(rt2).to.have.members(["piyo", "puyo", "poyo"]);
325
325
  });
326
326
  it("should accept absolute src dirname and relative dst dirname", async ()=>{
327
- await recv(hostInfo, [path.posix.join(remoteHome, remoteRoot)], localEmptyDir);
327
+ await recv(hostInfo, [path.posix.join(remoteHome, remoteRoot)], localEmptyDir, [], 0);
328
328
 
329
329
  let rt2 = await fs.readdir(path.posix.join(localEmptyDir, remoteRoot));
330
330
  expect(rt2).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
@@ -332,7 +332,7 @@ describe("test rsync exec", function() {
332
332
  expect(rt2).to.have.members(["piyo", "puyo", "poyo"]);
333
333
  });
334
334
  it("should accept relative src dirname and absolute dst dirname", async ()=>{
335
- await recv(hostInfo, [remoteRoot], path.resolve(localEmptyDir));
335
+ await recv(hostInfo, [remoteRoot], path.resolve(localEmptyDir), [], 0);
336
336
 
337
337
  let rt2 = await fs.readdir(path.posix.join(localEmptyDir, remoteRoot));
338
338
  expect(rt2).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
@@ -340,7 +340,7 @@ describe("test rsync exec", function() {
340
340
  expect(rt2).to.have.members(["piyo", "puyo", "poyo"]);
341
341
  });
342
342
  it("should accept absolute src dirname and absolute dst dirname", async ()=>{
343
- await recv(hostInfo, [path.posix.join(remoteHome, remoteRoot)], path.resolve(localEmptyDir));
343
+ await recv(hostInfo, [path.posix.join(remoteHome, remoteRoot)], path.resolve(localEmptyDir), [], 0);
344
344
 
345
345
  let rt2 = await fs.readdir(path.posix.join(localEmptyDir, remoteRoot));
346
346
  expect(rt2).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
@@ -348,7 +348,7 @@ describe("test rsync exec", function() {
348
348
  expect(rt2).to.have.members(["piyo", "puyo", "poyo"]);
349
349
  });
350
350
  it.skip("[not implemented] should recv files which matches only filter", async ()=>{
351
- await recv(hostInfo, [remoteRoot], localEmptyDir, "*/{ba*,hoge/*}");
351
+ await recv(hostInfo, [remoteRoot], localEmptyDir, "*/{ba*,hoge/*}", [], 0);
352
352
 
353
353
  let rt2 = await fs.readdir(path.posix.join(localEmptyDir, remoteRoot));
354
354
  expect(rt2).to.have.members(["bar", "baz", "hoge"]);
@@ -356,13 +356,13 @@ describe("test rsync exec", function() {
356
356
  expect(rt2).to.have.members(["piyo", "puyo", "poyo"]);
357
357
  });
358
358
  it("should not recv files which matches exclude filter", async ()=>{
359
- await recv(hostInfo, [remoteRoot], localEmptyDir, ["--exclude=*/ba*", "--exclude=hoge"]);
359
+ await recv(hostInfo, [remoteRoot], localEmptyDir, ["--exclude=*/ba*", "--exclude=hoge"], [], 0);
360
360
 
361
361
  const rt2 = await fs.readdir(path.posix.join(localEmptyDir, remoteRoot));
362
362
  expect(rt2).to.have.members(["foo", "huga"]);
363
363
  });
364
364
  it.skip("[not implemented] should recv files which matches only filter but should not recv which matches exclude filter", async ()=>{
365
- await recv(hostInfo, [remoteRoot], localEmptyDir, "*/{ba*,hoge/*}", "**/piyo");
365
+ await recv(hostInfo, [remoteRoot], localEmptyDir, "*/{ba*,hoge/*}", "**/piyo", [], 0);
366
366
 
367
367
  let rt2 = await fs.readdir(path.posix.join(localEmptyDir, remoteRoot));
368
368
  expect(rt2).to.have.members(["bar", "baz", "hoge"]);
@@ -372,7 +372,7 @@ describe("test rsync exec", function() {
372
372
  });
373
373
  describe("error case", ()=>{
374
374
  it("should not send directory to existing file path", ()=>{
375
- return expect(recv(hostInfo, [remoteRoot], localFiles[0])).to.be.rejected;
375
+ return expect(recv(hostInfo, [remoteRoot], localFiles[0], [], 0)).to.be.rejected;
376
376
  });
377
377
  });
378
378
  });
package/test/sshExec.js CHANGED
@@ -36,14 +36,14 @@ describe("test for ssh execution", function() {
36
36
  describe("#exec", ()=>{
37
37
  const testText = "hoge";
38
38
  it("should execute single command with stdout", async ()=>{
39
- const rt = await sshExec(hostInfo, `echo ${testText}`, sshout);
39
+ const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout);
40
40
  expect(rt).to.equal(0);
41
41
  expect(sshout).to.be.called;
42
42
 
43
43
  expect(sshout).to.be.calledWithMatch(/^hoge/);
44
44
  });
45
45
  it("should execute single command with stderr", async ()=>{
46
- const rt = await sshExec(hostInfo, `echo ${testText} >&2`, sshout);
46
+ const rt = await sshExec(hostInfo, `echo ${testText} >&2`, 0, sshout);
47
47
  expect(rt).to.equal(0);
48
48
  expect(sshout).to.be.called;
49
49
  expect(sshout).to.be.calledWithMatch(/^hoge/);
@@ -61,6 +61,9 @@ describe("test for ssh execution", function() {
61
61
  const rt = await sshExec(hostInfo, `./${nonExisting}`);
62
62
  expect(rt).to.equal(127);
63
63
  });
64
+ it("should be rejected if timeout expired", async ()=>{
65
+ return expect(sshExec(hostInfo, "sleep 60", 1)).to.be.rejectedWith("watchdog timer expired");
66
+ });
64
67
  });
65
68
  describe("#canConnect", ()=>{
66
69
  let hostInfo2;
@@ -106,31 +109,31 @@ describe("test for ssh execution", function() {
106
109
  await createRemoteFiles(hostInfo);
107
110
  });
108
111
  it("should return array of file and directory names in the specified directory", async ()=>{
109
- expect(await ls(hostInfo, remoteRoot)).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
112
+ expect(await ls(hostInfo, remoteRoot, [], 0)).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
110
113
  });
111
114
  it("should return array which has only specified file", async ()=>{
112
- expect(await ls(hostInfo, path.posix.join(remoteRoot, "foo"))).to.eql([path.posix.join(remoteRoot, "foo")]);
115
+ expect(await ls(hostInfo, path.posix.join(remoteRoot, "foo"), [], 0)).to.eql([path.posix.join(remoteRoot, "foo")]);
113
116
  });
114
117
  it("should return 'No such file or directory' if non-existing path is specified", async ()=>{
115
- expect(await ls(hostInfo, path.posix.join(remoteRoot, nonExisting))).to.match(/No such file or directory/);
118
+ expect(await ls(hostInfo, path.posix.join(remoteRoot, nonExisting), [], 0)).to.match(/No such file or directory/);
116
119
  });
117
120
  it("should return only matched filenames, if glob is specified", async ()=>{
118
- expect(await ls(hostInfo, path.posix.join(remoteRoot, "b*"))).to.have.members(["bar", "baz"].map((e)=>{
121
+ expect(await ls(hostInfo, path.posix.join(remoteRoot, "b*"), [], 0)).to.have.members(["bar", "baz"].map((e)=>{
119
122
  return path.posix.join(remoteRoot, e);
120
123
  }));
121
124
  });
122
125
  it("should return only matched filenames src contains multipl glob pattern", async ()=>{
123
- expect(await ls(hostInfo, path.posix.join(remoteRoot, "h*", "p[iu]yo"))).to.have.members(["piyo", "puyo"].map((e)=>{
126
+ expect(await ls(hostInfo, path.posix.join(remoteRoot, "h*", "p[iu]yo"), [], 0)).to.have.members(["piyo", "puyo"].map((e)=>{
124
127
  return path.posix.join(remoteRoot, "hoge", e);
125
128
  }));
126
129
  });
127
130
  it("should return only matched filenames, if specified glob contains /", async ()=>{
128
- expect(await ls(hostInfo, path.posix.join(remoteRoot, "hoge/*yo"))).to.have.members(["piyo", "puyo", "poyo"].map((e)=>{
131
+ expect(await ls(hostInfo, path.posix.join(remoteRoot, "hoge/*yo"), [], 0)).to.have.members(["piyo", "puyo", "poyo"].map((e)=>{
129
132
  return path.posix.join(remoteRoot, "hoge", e);
130
133
  }));
131
134
  });
132
135
  it("should return empty array if specified target is empty directory", async ()=>{
133
- expect(await ls(hostInfo, remoteEmptyDir)).to.be.an("array").that.is.empty;
136
+ expect(await ls(hostInfo, remoteEmptyDir, [], 0)).to.be.an("array").that.is.empty;
134
137
  });
135
138
  });
136
139
  });