fix mount-require not reloading all require() modules (#28)
* npm audit fix
* add vscode debug launch configuration
* mount-require is only reloading the main source file, not any other required files #9
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..6c83c80
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,17 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "wskdebug",
+ "skipFiles": [
+ "<node_internals>/**"
+ ],
+ "program": "${workspaceFolder}/wskdebug.js"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 13cae92..d3a0705 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1887,9 +1887,9 @@
},
"dependencies": {
"minimist": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
- "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
}
}
diff --git a/src/kinds/nodejs/mount-require.js b/src/kinds/nodejs/mount-require.js
index 0c3d19e..44be9f8 100644
--- a/src/kinds/nodejs/mount-require.js
+++ b/src/kinds/nodejs/mount-require.js
@@ -36,10 +36,16 @@
throw `'${mainFn}' is not a function in '${sourceFile}'. Specify the right function in wskdebug using --main.`;
}
+function clearEntireRequireCache() {
+ Object.keys(require.cache).forEach(function(key) {
+ delete require.cache[key];
+ });
+}
+
// eslint-disable-next-line no-unused-vars
function main(args) { // lgtm [js/unused-local-variable]
- // force reload of mounted action on every invocation
- delete require.cache[require.resolve(path)];
+ // force reload of entire mounted action on every invocation
+ clearEntireRequireCache();
// require and invoke main function
return require(path)[mainFn](args);
diff --git a/test/nodejs.test.js b/test/nodejs.test.js
index 6be9444..1f4b419 100644
--- a/test/nodejs.test.js
+++ b/test/nodejs.test.js
@@ -31,6 +31,10 @@
const test = require('./test');
const assert = require('assert');
const fse = require('fs-extra');
+const fs = require('fs');
+const os = require("os");
+const path = require("path");
+const sleep = require('util').promisify(setTimeout);
describe('nodejs', function() {
this.timeout(30000);
@@ -189,8 +193,6 @@
test.assertAllNocksInvoked();
});
-
-
it("should mount and run local sources with a comment on the last line", async function() {
test.mockActionAndInvocation(
"myaction",
@@ -417,6 +419,120 @@
test.assertAllNocksInvoked();
});
+ it("should reload local plain sources on file modification", async function() {
+ this.timeout(10000);
+
+ // create copy in temp dir so we can modify it
+ const tmpDir = path.join(os.tmpdir(), fs.mkdtempSync("wskdebug-test-"));
+ fse.copySync("test/nodejs/plain-flat", tmpDir);
+ process.chdir(tmpDir);
+
+ test.mockActionDoubleInvocation(
+ "myaction",
+ // should not use this code if we specify local sources which return CORRECT
+ `const main = () => ({ msg: 'WRONG' });`,
+ {},
+ { msg: "CORRECT" },
+ async () => {
+ // change action.js to test reloading
+ console.log("simulating modifiying action.js...");
+
+ fs.writeFileSync(`action.js`,
+ `
+ 'use strict';
+
+ function main(params) {
+ return { msg: "SECOND" };
+ }
+ `);
+
+ await sleep(1);
+ },
+ { msg: "SECOND" },
+ true // binary
+ );
+
+ await wskdebug(`myaction action.js -p ${test.port}`);
+
+ test.assertAllNocksInvoked();
+ });
+
+ it("should reload local commonjs sources on file modification", async function() {
+ this.timeout(10000);
+
+ // create copy in temp dir so we can modify it
+ const tmpDir = path.join(os.tmpdir(), fs.mkdtempSync("wskdebug-test-"));
+ fse.copySync("test/nodejs/commonjs-flat", tmpDir);
+ process.chdir(tmpDir);
+
+ test.mockActionDoubleInvocation(
+ "myaction",
+ // should not use this code if we specify local sources which return CORRECT
+ `const main = () => ({ msg: 'WRONG' });`,
+ {},
+ { msg: "CORRECT/RESULT" },
+ async () => {
+ // change action.js to test reloading
+ console.log("simulating modifiying action.js...");
+
+ fs.writeFileSync(`action.js`,
+ `
+ 'use strict';
+
+ exports.main = function() {
+ return { msg: "SECOND" };
+ }
+ `);
+
+ await sleep(1);
+ },
+ { msg: "SECOND" },
+ true // binary
+ );
+
+ await wskdebug(`myaction action.js -p ${test.port}`);
+
+ test.assertAllNocksInvoked();
+ });
+
+ it("should reload local commonjs sources with a require() dependency on file modification", async function() {
+ this.timeout(10000);
+
+ // create copy in temp dir so we can modify it
+ const tmpDir = path.join(os.tmpdir(), fs.mkdtempSync("wskdebug-test-"));
+ fse.copySync("test/nodejs/commonjs-deps", tmpDir);
+ process.chdir(tmpDir);
+
+ test.mockActionDoubleInvocation(
+ "myaction",
+ // should not use this code if we specify local sources which return CORRECT
+ `const main = () => ({ msg: 'WRONG' });`,
+ {},
+ { msg: "FIRST" },
+ async () => {
+ // change dependency.js to test reloading of require() deps
+ console.log("simulating modifiying depdency.js...");
+
+ fs.writeFileSync(`dependency.js`,
+ `
+ 'use strict';
+
+ module.exports = {
+ msg: "SECOND"
+ }
+ `);
+
+ await sleep(1);
+ },
+ { msg: "SECOND" },
+ true // binary
+ );
+
+ await wskdebug(`myaction action.js -p ${test.port}`);
+
+ test.assertAllNocksInvoked();
+ });
+
// TODO: test -l livereload connection
// TODO: test agents - conditions (unit test agent code locally)
diff --git a/test/nodejs/commonjs-deps/action.js b/test/nodejs/commonjs-deps/action.js
new file mode 100644
index 0000000..38aef01
--- /dev/null
+++ b/test/nodejs/commonjs-deps/action.js
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+exports.main = function() {
+ return require('./dependency');
+}
diff --git a/test/nodejs/commonjs-deps/dependency.js b/test/nodejs/commonjs-deps/dependency.js
new file mode 100644
index 0000000..11b6e46
--- /dev/null
+++ b/test/nodejs/commonjs-deps/dependency.js
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+module.exports = {
+ msg: "FIRST"
+}
diff --git a/test/test.js b/test/test.js
index b0c5360..81f5b23 100644
--- a/test/test.js
+++ b/test/test.js
@@ -221,6 +221,81 @@
expectAgentInvocation(action, params, expectedResult);
}
+function mockActionDoubleInvocation(action, code, params, result1, runBetween, result2, binary=false) {
+ params = params || {};
+ const activationId = Date.now();
+ result1.$activationId = activationId;
+
+ mockAction(action, code, binary);
+ expectAgent(action, code, binary);
+
+ // 1st activation
+ nockActivation(action, body => body.$waitForActivation === true)
+ .reply(200, {
+ response: {
+ result: Object.assign(params, { $activationId: activationId })
+ }
+ });
+
+ // wskdebug sending result back to agent
+ nockActivation(
+ action,
+ body => {
+ assert.deepStrictEqual(body, result1);
+ return true;
+ }
+ ).reply(200, {
+ response: {
+ result: {
+ message: "Completed"
+ }
+ }
+ });
+
+ // 2nd activation
+ const activationId2 = Date.now() + "-second";
+ result2.$activationId = activationId2;
+
+ nockActivation(action, body => body.$waitForActivation === true)
+ .reply(200, () => {
+ runBetween();
+ return {
+ response: {
+ result: Object.assign(params, { $activationId: activationId2 })
+ }
+ }
+ });
+
+ // wskdebug sending 2nd result back to agent
+ nockActivation(
+ action,
+ body => {
+ assert.deepStrictEqual(body, result2);
+ return true;
+ }
+ ).reply(200, {
+ response: {
+ result: {
+ message: "Completed"
+ }
+ }
+ });
+
+ // graceful shutdown for wskdebug to end test
+ nockActivation(action, body => body.$waitForActivation === true)
+ .reply(502, {
+ response: {
+ success: false,
+ result: {
+ error: {
+ error: "Please exit, thanks.",
+ code: 43 // graceful exit
+ }
+ }
+ }
+ });
+}
+
// --------------------------------------------< internal >---------------
function nodejsActionDescription(name, binary=false) {
@@ -410,6 +485,7 @@
assertAllNocksInvoked,
// mock
mockActionAndInvocation,
+ mockActionDoubleInvocation,
// advanced
mockAction,
expectAgent,