From aa6b72ac87fbedfa1f65c1a492c0af23de576c8b Mon Sep 17 00:00:00 2001 From: Hiroki Uchikawa Date: Thu, 15 Mar 2018 15:03:24 +0900 Subject: [PATCH 01/32] Move context.js to context folder and rename context.js -> index.js --- red/runtime/nodes/{context.js => context/index.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename red/runtime/nodes/{context.js => context/index.js} (100%) diff --git a/red/runtime/nodes/context.js b/red/runtime/nodes/context/index.js similarity index 100% rename from red/runtime/nodes/context.js rename to red/runtime/nodes/context/index.js From cd44f1317190d066d6cfb80a2581ffac74014a8f Mon Sep 17 00:00:00 2001 From: Hiroki Uchikawa Date: Thu, 15 Mar 2018 15:03:50 +0900 Subject: [PATCH 02/32] Move context_spec.js to context folder and rename context_spec.js -> index_spec.js --- test/red/runtime/nodes/{context_spec.js => context/index_spec.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/red/runtime/nodes/{context_spec.js => context/index_spec.js} (100%) diff --git a/test/red/runtime/nodes/context_spec.js b/test/red/runtime/nodes/context/index_spec.js similarity index 100% rename from test/red/runtime/nodes/context_spec.js rename to test/red/runtime/nodes/context/index_spec.js From 771b598c09c82f5ee716f003193b69ef65b2c8e4 Mon Sep 17 00:00:00 2001 From: Hiroki Uchikawa Date: Thu, 15 Mar 2018 15:15:26 +0900 Subject: [PATCH 03/32] Add persistable context and avoid exception when arg is undefined in util/getMessageProperty --- red/runtime/nodes/context/index.js | 50 ++- red/runtime/util.js | 2 +- test/red/runtime/nodes/context/index_spec.js | 330 ++++++++++++------- 3 files changed, 253 insertions(+), 129 deletions(-) diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index e71a1eaed..b63aedb17 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -14,20 +14,27 @@ * limitations under the License. **/ -var clone = require("clone"); -var when = require("when"); -var util = require("../util"); +var util = require("../../util"); +var log = require("../../log"); +var externalContext = require("./external"); + +var contexts = {}; +var globalContext = null; + +var re = /^(\$.*?)\.(.+)|^(\$.*)/; function createContext(id,seed) { + var flowId = id; var data = seed || {}; var obj = seed || {}; - obj.get = function get(key) { + + function get(key) { return util.getMessageProperty(data,key); }; - obj.set = function set(key, value) { + function set(key, value) { util.setMessageProperty(data,key,value); - } - obj.keys = function() { + }; + function keys() { var keysData = Object.keys(data); if (seed == null) { return keysData; @@ -36,13 +43,32 @@ function createContext(id,seed) { return key !== "set" && key !== "get" && key !== "keys"; }); } + }; + + obj.get = function(key) { + if(externalContext.canUse(key)) { + return externalContext.get(key, flowId); + }else{ + return get(key); + } + }; + obj.set = function(key, value) { + if(externalContext.canUse(key)) { + externalContext.set(key, value, flowId); + }else{ + set(key, value); + } + } + obj.keys = function(key) { + if(externalContext.canUse(key)) { + return externalContext.keys(key, flowId); + }else{ + return keys(); + } } return obj; } -var contexts = {}; -var globalContext = null; - function getContext(localId,flowId) { var contextId = localId; if (flowId) { @@ -61,6 +87,7 @@ function getContext(localId,flowId) { contexts[contextId] = newContext; return newContext; } + function deleteContext(id,flowId) { var contextId = id; if (flowId) { @@ -68,6 +95,7 @@ function deleteContext(id,flowId) { } delete contexts[contextId]; } + function clean(flowConfig) { var activeIds = {}; var contextId; @@ -81,9 +109,11 @@ function clean(flowConfig) { } } } + module.exports = { init: function(settings) { globalContext = createContext("global",settings.functionGlobalContext || {}); + externalContext.init(settings); }, get: getContext, delete: deleteContext, diff --git a/red/runtime/util.js b/red/runtime/util.js index 257e87777..4d077c21f 100644 --- a/red/runtime/util.js +++ b/red/runtime/util.js @@ -240,7 +240,7 @@ function getMessageProperty(msg,expr) { var msgPropParts = normalisePropertyExpression(expr); var m; msgPropParts.reduce(function(obj, key) { - result = (typeof obj[key] !== "undefined" ? obj[key] : undefined); + result = ((typeof obj !== "undefined") && (typeof obj[key] !== "undefined") ? obj[key] : undefined); return result; }, msg); return result; diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index 3e7c03de4..fce39bcf4 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -16,129 +16,223 @@ var should = require("should"); var sinon = require('sinon'); -var Context = require("../../../../red/runtime/nodes/context"); +var path = require('path'); +var fs = require('fs-extra'); +var Context = require("../../../../../red/runtime/nodes/context/index"); describe('context', function() { - beforeEach(function() { - Context.init({}); - }); - afterEach(function() { - Context.clean({allNodes:{}}); - }); - it('stores local property',function() { - var context1 = Context.get("1","flowA"); - should.not.exist(context1.get("foo")); - context1.set("foo","test"); - context1.get("foo").should.eql("test"); - }); - it('stores local property - creates parent properties',function() { - var context1 = Context.get("1","flowA"); - context1.set("foo.bar","test"); - context1.get("foo").should.eql({bar:"test"}); - }); - it('deletes local property',function() { - var context1 = Context.get("1","flowA"); - context1.set("foo.abc.bar1","test1"); - context1.set("foo.abc.bar2","test2"); - context1.get("foo.abc").should.eql({bar1:"test1",bar2:"test2"}); - context1.set("foo.abc.bar1",undefined); - context1.get("foo.abc").should.eql({bar2:"test2"}); - context1.set("foo.abc",undefined); - should.not.exist(context1.get("foo.abc")); - context1.set("foo",undefined); - should.not.exist(context1.get("foo")); - }); - it('stores flow property',function() { - var context1 = Context.get("1","flowA"); - should.not.exist(context1.flow.get("foo")); - context1.flow.set("foo","test"); - context1.flow.get("foo").should.eql("test"); - }); - it('stores global property',function() { - var context1 = Context.get("1","flowA"); - should.not.exist(context1.global.get("foo")); - context1.global.set("foo","test"); - context1.global.get("foo").should.eql("test"); + describe('localmemory',function() { + beforeEach(function() { + Context.init({}); + }); + afterEach(function() { + Context.clean({allNodes:{}}); + }); + it('stores local property',function() { + var context1 = Context.get("1","flowA"); + should.not.exist(context1.get("foo")); + context1.set("foo","test"); + context1.get("foo").should.eql("test"); + }); + it('stores local property - creates parent properties',function() { + var context1 = Context.get("1","flowA"); + context1.set("foo.bar","test"); + context1.get("foo").should.eql({bar:"test"}); + }); + it('deletes local property',function() { + var context1 = Context.get("1","flowA"); + context1.set("foo.abc.bar1","test1"); + context1.set("foo.abc.bar2","test2"); + context1.get("foo.abc").should.eql({bar1:"test1",bar2:"test2"}); + context1.set("foo.abc.bar1",undefined); + context1.get("foo.abc").should.eql({bar2:"test2"}); + context1.set("foo.abc",undefined); + should.not.exist(context1.get("foo.abc")); + context1.set("foo",undefined); + should.not.exist(context1.get("foo")); + }); + it('stores flow property',function() { + var context1 = Context.get("1","flowA"); + should.not.exist(context1.flow.get("foo")); + context1.flow.set("foo","test"); + context1.flow.get("foo").should.eql("test"); + }); + it('stores global property',function() { + var context1 = Context.get("1","flowA"); + should.not.exist(context1.global.get("foo")); + context1.global.set("foo","test"); + context1.global.get("foo").should.eql("test"); + }); + + it('keeps local context local', function() { + var context1 = Context.get("1","flowA"); + var context2 = Context.get("2","flowA"); + + should.not.exist(context1.get("foo")); + should.not.exist(context2.get("foo")); + context1.set("foo","test"); + + context1.get("foo").should.eql("test"); + should.not.exist(context2.get("foo")); + }); + it('flow context accessible to all flow nodes', function() { + var context1 = Context.get("1","flowA"); + var context2 = Context.get("2","flowA"); + + should.not.exist(context1.flow.get("foo")); + should.not.exist(context2.flow.get("foo")); + + context1.flow.set("foo","test"); + context1.flow.get("foo").should.eql("test"); + context2.flow.get("foo").should.eql("test"); + }); + + it('flow context not shared to nodes on other flows', function() { + var context1 = Context.get("1","flowA"); + var context2 = Context.get("2","flowB"); + + should.not.exist(context1.flow.get("foo")); + should.not.exist(context2.flow.get("foo")); + + context1.flow.set("foo","test"); + context1.flow.get("foo").should.eql("test"); + should.not.exist(context2.flow.get("foo")); + }); + + it('global context shared to all nodes', function() { + var context1 = Context.get("1","flowA"); + var context2 = Context.get("2","flowB"); + + should.not.exist(context1.global.get("foo")); + should.not.exist(context2.global.get("foo")); + + context1.global.set("foo","test"); + context1.global.get("foo").should.eql("test"); + context2.global.get("foo").should.eql("test"); + }); + + it('deletes context',function() { + var context = Context.get("1","flowA"); + should.not.exist(context.get("foo")); + context.set("foo","abc"); + context.get("foo").should.eql("abc"); + + Context.delete("1","flowA"); + context = Context.get("1","flowA"); + should.not.exist(context.get("foo")); + }); + + it('enumerates context keys', function() { + var context = Context.get("1","flowA"); + + var keys = context.keys(); + keys.should.be.an.Array(); + keys.should.be.empty(); + + context.set("foo","bar"); + keys = context.keys(); + keys.should.have.length(1); + keys[0].should.eql("foo"); + + context.set("abc.def","bar"); + keys = context.keys(); + keys.should.have.length(2); + keys[1].should.eql("abc"); + }); }); - it('keeps local context local', function() { - var context1 = Context.get("1","flowA"); - var context2 = Context.get("2","flowA"); + describe('external context storage',function() { + var testDir = path.join(__dirname,".testUserHome"); + var context; + var stubGet = sinon.stub(); + var stubSet = sinon.stub(); + var stubKeys = sinon.stub(); + var contextStorage={ + test:{ + module: { + init: function() { + return true; + }, + get: stubGet, + set: stubSet, + keys: stubKeys, + }, + config:{} + } + }; - should.not.exist(context1.get("foo")); - should.not.exist(context2.get("foo")); - context1.set("foo","test"); + beforeEach(function() { + Context.init({contextStorage:contextStorage}); + context = Context.get("1","flow"); + }); + afterEach(function(done) { + stubGet.reset(); + stubSet.reset(); + stubKeys.reset(); + Context.clean({allNodes:{}}); + fs.remove(testDir,done); + }); - context1.get("foo").should.eql("test"); - should.not.exist(context2.get("foo")); + describe('if external context storage exists',function() { + it('should store local property to external context storage',function() { + should.not.exist(context.get("$test.foo")); + context.set("$test.foo","test"); + context.get("$test.foo"); + context.keys("$test"); + stubGet.called.should.be.true(); + stubSet.called.should.be.true(); + stubKeys.called.should.be.true(); + }); + it('should store flow property to external context storage',function() { + should.not.exist(context.flow.get("$test.foo")); + context.flow.set("$test.foo","test"); + context.flow.get("$test.foo"); + context.flow.keys("$test"); + stubGet.called.should.be.true(); + stubSet.called.should.be.true(); + stubKeys.called.should.be.true(); + }); + it('should store global property to external context storage',function() { + should.not.exist(context.global.get("$test.foo")); + context.global.set("$test.foo","test"); + context.global.get("$test.foo"); + context.global.keys("$test"); + stubGet.called.should.be.true(); + stubSet.called.should.be.true(); + stubKeys.called.should.be.true(); + }); + }); + + describe('if external context storage does not exist',function() { + it('should store local property to local memory',function() { + should.not.exist(context.flow.get("$nonexist.foo")); + context.set("$nonexist.foo","test"); + context.get("$nonexist.foo").should.eql("test"); + context.keys("$nonexist").should.have.length(1); + stubGet.notCalled.should.be.true(); + stubSet.notCalled.should.be.true(); + stubKeys.notCalled.should.be.true(); + }); + + it('should store flow property to local memory',function() { + should.not.exist(context.flow.get("$nonexist.foo")); + context.flow.set("$nonexist.foo","test"); + context.flow.get("$nonexist.foo").should.eql("test"); + context.flow.keys("$nonexist").should.have.length(1); + stubGet.notCalled.should.be.true(); + stubSet.notCalled.should.be.true(); + stubKeys.notCalled.should.be.true(); + }); + + it('should store global property to local memory',function() { + should.not.exist(context.global.get("$nonexist.foo")); + context.global.set("$nonexist.foo","test"); + context.global.get("$nonexist.foo").should.eql("test"); + context.global.keys("$nonexist").should.have.length(1); + stubGet.notCalled.should.be.true(); + stubSet.notCalled.should.be.true(); + stubKeys.notCalled.should.be.true(); + }); + }); }); - it('flow context accessible to all flow nodes', function() { - var context1 = Context.get("1","flowA"); - var context2 = Context.get("2","flowA"); - - should.not.exist(context1.flow.get("foo")); - should.not.exist(context2.flow.get("foo")); - - context1.flow.set("foo","test"); - context1.flow.get("foo").should.eql("test"); - context2.flow.get("foo").should.eql("test"); - }); - - it('flow context not shared to nodes on other flows', function() { - var context1 = Context.get("1","flowA"); - var context2 = Context.get("2","flowB"); - - should.not.exist(context1.flow.get("foo")); - should.not.exist(context2.flow.get("foo")); - - context1.flow.set("foo","test"); - context1.flow.get("foo").should.eql("test"); - should.not.exist(context2.flow.get("foo")); - }); - - it('global context shared to all nodes', function() { - var context1 = Context.get("1","flowA"); - var context2 = Context.get("2","flowB"); - - should.not.exist(context1.global.get("foo")); - should.not.exist(context2.global.get("foo")); - - context1.global.set("foo","test"); - context1.global.get("foo").should.eql("test"); - context2.global.get("foo").should.eql("test"); - }); - - it('deletes context',function() { - var context = Context.get("1","flowA"); - should.not.exist(context.get("foo")); - context.set("foo","abc"); - context.get("foo").should.eql("abc"); - - Context.delete("1","flowA"); - context = Context.get("1","flowA"); - should.not.exist(context.get("foo")); - }) - - it('enumerates context keys', function() { - var context = Context.get("1","flowA"); - - var keys = context.keys(); - keys.should.be.an.Array(); - keys.should.be.empty(); - - context.set("foo","bar"); - keys = context.keys(); - keys.should.have.length(1); - keys[0].should.eql("foo"); - - context.set("abc.def","bar"); - keys = context.keys(); - keys.should.have.length(2); - keys[1].should.eql("abc"); - - - - - }) - }); From e66b3810702d6f9dc99ae56068ae42a07f2cbeab Mon Sep 17 00:00:00 2001 From: Hiroki Uchikawa Date: Fri, 16 Mar 2018 05:52:17 +0900 Subject: [PATCH 04/32] add external context files --- red/runtime/nodes/context/external.js | 133 +++++++++++ red/runtime/nodes/context/localfilesystem.js | 81 +++++++ .../runtime/nodes/context/external_spec.js | 222 ++++++++++++++++++ .../nodes/context/localfilesystem_spec.js | 0 4 files changed, 436 insertions(+) create mode 100644 red/runtime/nodes/context/external.js create mode 100644 red/runtime/nodes/context/localfilesystem.js create mode 100644 test/red/runtime/nodes/context/external_spec.js create mode 100644 test/red/runtime/nodes/context/localfilesystem_spec.js diff --git a/red/runtime/nodes/context/external.js b/red/runtime/nodes/context/external.js new file mode 100644 index 000000000..d969fc53d --- /dev/null +++ b/red/runtime/nodes/context/external.js @@ -0,0 +1,133 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed 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. + **/ + +var when = require("when"); +var log = require("../../log"); + +var re = /^(\$.*?)\.(.+)|^(\$.*)/; +var externalContexts; + +function parseKey(key){ + var keys = null; + var temp = re.exec(key); + if(temp){ + keys = []; + if(temp[3]){ + keys[0] = temp[3]; + keys[1] = null; + } else { + keys[0] = temp[1]; + keys[1] = temp[2]; + } + keys[0] = keys[0] === "$" ? "default" : keys[0].substring(1); + } + return keys; +} + +function hasContextStorage(plugin) { + return externalContexts.hasOwnProperty(plugin); +} + +var contextModuleInterface ={ + init: function (settings) { + var plugins = settings.contextStorage; + externalContexts = {}; + if (plugins) { + for(var pluginName in plugins){ + var plugin; + if(plugins[pluginName].hasOwnProperty("module") && plugins[pluginName].hasOwnProperty("config")) { + if(typeof plugins[pluginName].module === "string") { + try{ + plugin = require(plugins[pluginName].module); + }catch(err){ + log.error(err); + continue; + } + } else { + plugin = plugins[pluginName].module; + } + plugin.init(plugins[pluginName].config); + externalContexts[pluginName] = plugin; + } + } + } + }, + get: function(key, flowId) { + var result = parseKey(key); + if(!result){ + throw new Error("Invalid key"); + } + if(hasContextStorage(result[0])){ + return externalContexts[result[0]].get(result[1], flowId); + }else if(hasContextStorage("default")) { + // log.warn(result[1] " is got from default context storage"); + return externalContexts["default"].get(result[1], flowId); + }else{ + throw new Error(result[0] + " is not defined in setting.js"); + } + + }, + set: function(key, value, flowId) { + var result = parseKey(key); + if(!result){ + throw new Error("Invalid key"); + } + if(hasContextStorage(result[0])){ + externalContexts[result[0]].set(result[1], value, flowId); + }else if(hasContextStorage("default")) { + // log.warn(result[1] " is set to default context storage"); + externalContexts["default"].set(result[1], value, flowId); + }else{ + throw new Error(result[0] + " is not defined in setting.js"); + } + }, + keys: function(key, flowId) { + var result = parseKey(key); + if(!result){ + throw new Error("Invalid key"); + } + if(hasContextStorage(result[0])){ + return externalContexts[result[0]].keys(flowId); + }else if(hasContextStorage("default")) { + // log.warn("keys are got from default context storage"); + externalContexts["default"].keys(flowId); + }else{ + throw new Error(result[0] + " is not defined in setting.js"); + } + }, + // run: function(command,key,value,flowId) { + // //todo: run custom method in plugin + // }, + // close: function(){ + // //todo: close connections, streams, etc... + // }, + canUse: function(key){ + var result = parseKey(key); + if(!result){ + return false; + }else{ + if(hasContextStorage(result[0]) || hasContextStorage("default")){ + return true; + }else{ + return false; + } + } + }, + hasContextStorage: hasContextStorage, + parseKey: parseKey +}; + +module.exports = contextModuleInterface; \ No newline at end of file diff --git a/red/runtime/nodes/context/localfilesystem.js b/red/runtime/nodes/context/localfilesystem.js new file mode 100644 index 000000000..733c619d1 --- /dev/null +++ b/red/runtime/nodes/context/localfilesystem.js @@ -0,0 +1,81 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed 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. + **/ + +var storage = require('node-persist'); +var fs = require('fs-extra'); +var fspath = require("path"); + +var configs; +var storagePath; +var fileStorages; + +function createStorage(path) { + var fileStorage = storage.create({dir: fspath.join(storagePath,path)}); + fileStorage.initSync(); + fileStorages[path] = fileStorage; +} + +var localfilesystem = { + init: function(_configs) { + configs = _configs; + fileStorages = {}; + if (!configs.dir) { + try { + fs.statSync(fspath.join(process.env.NODE_RED_HOME,".config.json")); + storagePath = fspath.join(process.env.NODE_RED_HOME,"contexts"); + } catch(err) { + try { + // Consider compatibility for older versions + if (process.env.HOMEPATH) { + fs.statSync(fspath.join(process.env.HOMEPATH,".node-red",".config.json")); + storagePath = fspath.join(process.env.HOMEPATH,".node-red","contexts"); + } + } catch(err) { + } + if (!configs.dir) { + storagePath = fspath.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || process.env.NODE_RED_HOME,".node-red","contexts"); + } + } + }else{ + storagePath = configs.dir; + } + + }, + stop: function() { + return when.resolve(); + }, + get: function (key, flowId) { + if(!fileStorages[flowId]){ + createStorage(flowId); + } + return fileStorages[flowId].getItemSync(key); + }, + + set: function (key, value, flowId) { + if(!fileStorages[flowId]){ + createStorage(flowId); + } + fileStorages[flowId].setItemSync(key, value); + }, + keys: function (flowId) { + if(!fileStorages[flowId]){ + createStorage(flowId); + } + return fileStorages[flowId].keys(); + } +}; + +module.exports = localfilesystem; diff --git a/test/red/runtime/nodes/context/external_spec.js b/test/red/runtime/nodes/context/external_spec.js new file mode 100644 index 000000000..726d1c666 --- /dev/null +++ b/test/red/runtime/nodes/context/external_spec.js @@ -0,0 +1,222 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed 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. + **/ +var should = require("should"); +var sinon = require('sinon'); +var external = require("../../../../../red/runtime/nodes/context/external"); + +describe("external", function() { + var stubModule = { + module: { + init: function(){}, + get: function(){}, + set: function(){}, + keys: function(){}, + run: function(){}, + close: function(){} + }, + config: {} + }; + describe('#init()', function() { + it('should load bundle module as default', function() { + external.init({ + contextStorage:{ + default:{ + module: "./localfilesystem", + config:{} + } + }}); + external.hasContextStorage("default").should.be.true(); + }); + + it('should load bundle module as localfile', function() { + external.init({ + contextStorage:{ + localfile:{ + module: "./localfilesystem", + config:{} + } + }}); + external.hasContextStorage("localfile").should.be.true(); + }); + + it('should not load non-existent module', function() { + external.init({ + contextStorage:{ + default:{ + module: "non-existent-module", + config:{} + } + + }}) + external.hasContextStorage("default").should.be.false(); + }); + + it('should load multiple modules', function() { + external.init({ + contextStorage:{ + default:{ + module: "./localfilesystem", + config:{} + }, + test:{ + module: { + init: function() { + return true; + } + }, + config:{} + } + } + }); + external.hasContextStorage("default").should.be.true(); + external.hasContextStorage("test").should.be.true(); + }); + + it('should load multiple modules without non-existent module', function() { + external.init({ + contextStorage:{ + nonexist:{ + module: "non-existent-module", + config:{} + }, + default:{ + module: "./localfilesystem", + config:{} + }, + test:{ + module: { + init: function() { + return true; + } + }, + config:{} + } + } + }); + external.hasContextStorage("nonexist").should.be.false(); + external.hasContextStorage("default").should.be.true(); + external.hasContextStorage("test").should.be.true(); + }); + }); + + // describe('#get()', function() { + // }); + + // describe('#set()', function() { + // }); + + // describe('#keys()', function() { + // }); + + // describe('#hasContextStorage()', function() { + // }); + + describe('#canUse()', function() { + it('should return true if specified module is loaded', function() { + external.init({ + contextStorage:{ + localfilesystem:{ + module: {name:"test",init: function(){return true}}, + config: {} + } + } + }); + external.canUse("$localfilesystem").should.be.true(); + external.canUse("$localfilesystem.foo").should.be.true(); + }); + it('should return false if specified module is not loaded', function() { + external.init({ + contextStorage:{ + localfilesystem:{ + module: {name:"test",init: function(){return true}}, + config: {} + } + } + }); + external.canUse("$file").should.be.false(); + external.canUse("$file.foo").should.be.false(); + }); + it('should return true if specified module is not loaded but default module is loaded', function() { + external.init({ + contextStorage:{ + default:{ + module: {name:"test",init: function(){return true}}, + config: {} + } + } + }); + external.canUse("$file").should.be.true(); + external.canUse("$file.foo").should.be.true(); + }); + it('should return false if argument does not contain module name', function() { + external.init({ + contextStorage:{ + default:{ + module: {name:"test",init: function(){return true}}, + config: {} + } + } + }); + external.canUse("file").should.be.false(); + external.canUse("file.foo").should.be.false(); + }); + }); + + describe('#parseKey()', function() { + function returnModuleAndKey(input, expectedModule, expectedKey) { + var result = external.parseKey(input); + result[0].should.eql(expectedModule); + result[1].should.eql(expectedKey); + }; + + function returnModule(input, expectedModule) { + var result = external.parseKey(input); + result[0].should.eql(expectedModule); + should(result[1]).be.null(); + }; + + it('should retrun module and key', function() { + returnModuleAndKey("$test.aaa","test","aaa"); + returnModuleAndKey("$test.aaa.bbb","test","aaa.bbb"); + returnModuleAndKey("$1.234","1","234"); + returnModuleAndKey("$$test.foo","$test","foo"); + returnModuleAndKey("$test.$foo","test","$foo"); + returnModuleAndKey("$test.$foo.$bar","test","$foo.$bar"); + returnModuleAndKey("$test..foo","test",".foo"); + returnModuleAndKey("$test..","test","."); + }); + + it('should retrun only module', function() { + returnModule("$test","test",null); + returnModule("$1","1",null); + returnModule("$$test","$test",null); + returnModule("$test.","test.",null); + }); + + it('should retrun module as default', function() { + returnModuleAndKey("$default.foo","default","foo"); + returnModuleAndKey("$.foo","default","foo"); + returnModule("$default","default"); + returnModule("$","default"); + }); + + it('should retrun null', function() { + should(external.parseKey("test.aaa")).be.null(); + should(external.parseKey("test")).be.null(); + should(external.parseKey(null)).be.null(); + }); + }); +}); \ No newline at end of file diff --git a/test/red/runtime/nodes/context/localfilesystem_spec.js b/test/red/runtime/nodes/context/localfilesystem_spec.js new file mode 100644 index 000000000..e69de29bb From b4b70a988ee10da5386529b80a2b5c0027f547ad Mon Sep 17 00:00:00 2001 From: Hiroki Uchikawa Date: Fri, 16 Mar 2018 06:06:06 +0900 Subject: [PATCH 05/32] Change delimiter to "_" from ":" --- red/runtime/nodes/context/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index b63aedb17..cd78f25de 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -72,7 +72,7 @@ function createContext(id,seed) { function getContext(localId,flowId) { var contextId = localId; if (flowId) { - contextId = localId+":"+flowId; + contextId = localId+"_"+flowId; } if (contexts.hasOwnProperty(contextId)) { return contexts[contextId]; @@ -91,7 +91,7 @@ function getContext(localId,flowId) { function deleteContext(id,flowId) { var contextId = id; if (flowId) { - contextId = id+":"+flowId; + contextId = id+"_"+flowId; } delete contexts[contextId]; } @@ -102,7 +102,7 @@ function clean(flowConfig) { var node; for (var id in contexts) { if (contexts.hasOwnProperty(id)) { - var idParts = id.split(":"); + var idParts = id.split("_"); if (!flowConfig.allNodes.hasOwnProperty(idParts[0])) { delete contexts[id]; } From e33ec0cf505fc9429e8fb3dfde74ac50be24702f Mon Sep 17 00:00:00 2001 From: Hiroki Uchikawa Date: Fri, 23 Mar 2018 17:18:56 +0900 Subject: [PATCH 06/32] update external context - Implement `delete` function - Swap default easily - Change memory context as a plugin - Update localfilesystem plugin - Change file/folder structure --- package.json | 2 + red/runtime/index.js | 2 + red/runtime/nodes/context/external.js | 133 ----------- red/runtime/nodes/context/index.js | 167 +++++++++---- red/runtime/nodes/context/localfilesystem.js | 114 ++++++--- red/runtime/nodes/context/memory.js | 59 +++++ red/runtime/nodes/index.js | 5 +- .../runtime/nodes/context/external_spec.js | 222 ------------------ test/red/runtime/nodes/context/index_spec.js | 51 ++++ .../nodes/context/localfilesystem_spec.js | 126 ++++++++++ test/red/runtime/nodes/context/memory_spec.js | 141 +++++++++++ 11 files changed, 581 insertions(+), 441 deletions(-) delete mode 100644 red/runtime/nodes/context/external.js create mode 100644 red/runtime/nodes/context/memory.js delete mode 100644 test/red/runtime/nodes/context/external_spec.js create mode 100644 test/red/runtime/nodes/context/memory_spec.js diff --git a/package.json b/package.json index be2fec970..bd52aa85a 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "mqtt": "2.18.0", "multer": "1.3.0", "mustache": "2.3.0", + "node-json-db": "0.7.5", "node-red-node-email": "0.1.*", "node-red-node-feedparser": "0.1.*", "node-red-node-rbe": "0.2.*", @@ -101,6 +102,7 @@ "http-proxy": "^1.16.2", "istanbul": "0.4.5", "mocha": "^5.1.1", + "rewire": "3.0.2", "should": "^8.4.0", "sinon": "1.17.7", "stoppable": "^1.0.6", diff --git a/red/runtime/index.js b/red/runtime/index.js index 6577ee59c..30d9c6f2b 100644 --- a/red/runtime/index.js +++ b/red/runtime/index.js @@ -109,6 +109,8 @@ function start() { events.emit("runtime-event",{id:"runtime-unsupported-version",payload:{type:"error",text:"notification.errors.unsupportedVersion"},retain:true}); } log.info(os.type()+" "+os.release()+" "+os.arch()+" "+os.endianness()); + log.info("Loading external context plugins"); + redNodes.loadContextsPlugin(); return redNodes.load().then(function() { var i; diff --git a/red/runtime/nodes/context/external.js b/red/runtime/nodes/context/external.js deleted file mode 100644 index d969fc53d..000000000 --- a/red/runtime/nodes/context/external.js +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright JS Foundation and other contributors, http://js.foundation - * - * Licensed 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. - **/ - -var when = require("when"); -var log = require("../../log"); - -var re = /^(\$.*?)\.(.+)|^(\$.*)/; -var externalContexts; - -function parseKey(key){ - var keys = null; - var temp = re.exec(key); - if(temp){ - keys = []; - if(temp[3]){ - keys[0] = temp[3]; - keys[1] = null; - } else { - keys[0] = temp[1]; - keys[1] = temp[2]; - } - keys[0] = keys[0] === "$" ? "default" : keys[0].substring(1); - } - return keys; -} - -function hasContextStorage(plugin) { - return externalContexts.hasOwnProperty(plugin); -} - -var contextModuleInterface ={ - init: function (settings) { - var plugins = settings.contextStorage; - externalContexts = {}; - if (plugins) { - for(var pluginName in plugins){ - var plugin; - if(plugins[pluginName].hasOwnProperty("module") && plugins[pluginName].hasOwnProperty("config")) { - if(typeof plugins[pluginName].module === "string") { - try{ - plugin = require(plugins[pluginName].module); - }catch(err){ - log.error(err); - continue; - } - } else { - plugin = plugins[pluginName].module; - } - plugin.init(plugins[pluginName].config); - externalContexts[pluginName] = plugin; - } - } - } - }, - get: function(key, flowId) { - var result = parseKey(key); - if(!result){ - throw new Error("Invalid key"); - } - if(hasContextStorage(result[0])){ - return externalContexts[result[0]].get(result[1], flowId); - }else if(hasContextStorage("default")) { - // log.warn(result[1] " is got from default context storage"); - return externalContexts["default"].get(result[1], flowId); - }else{ - throw new Error(result[0] + " is not defined in setting.js"); - } - - }, - set: function(key, value, flowId) { - var result = parseKey(key); - if(!result){ - throw new Error("Invalid key"); - } - if(hasContextStorage(result[0])){ - externalContexts[result[0]].set(result[1], value, flowId); - }else if(hasContextStorage("default")) { - // log.warn(result[1] " is set to default context storage"); - externalContexts["default"].set(result[1], value, flowId); - }else{ - throw new Error(result[0] + " is not defined in setting.js"); - } - }, - keys: function(key, flowId) { - var result = parseKey(key); - if(!result){ - throw new Error("Invalid key"); - } - if(hasContextStorage(result[0])){ - return externalContexts[result[0]].keys(flowId); - }else if(hasContextStorage("default")) { - // log.warn("keys are got from default context storage"); - externalContexts["default"].keys(flowId); - }else{ - throw new Error(result[0] + " is not defined in setting.js"); - } - }, - // run: function(command,key,value,flowId) { - // //todo: run custom method in plugin - // }, - // close: function(){ - // //todo: close connections, streams, etc... - // }, - canUse: function(key){ - var result = parseKey(key); - if(!result){ - return false; - }else{ - if(hasContextStorage(result[0]) || hasContextStorage("default")){ - return true; - }else{ - return false; - } - } - }, - hasContextStorage: hasContextStorage, - parseKey: parseKey -}; - -module.exports = contextModuleInterface; \ No newline at end of file diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index cd78f25de..845408e8f 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -14,57 +14,110 @@ * limitations under the License. **/ +var clone = require("clone"); var util = require("../../util"); -var log = require("../../log"); -var externalContext = require("./external"); +var settings; var contexts = {}; var globalContext = null; +var externalContexts = {}; -var re = /^(\$.*?)\.(.+)|^(\$.*)/; +function init(_settings) { + settings = _settings; + externalContexts = {}; -function createContext(id,seed) { - var flowId = id; - var data = seed || {}; - var obj = seed || {}; + // init meomory plugin + externalContexts["_"] = require("./memory"); + externalContexts["_"].init(); + globalContext = createContext("global",settings.functionGlobalContext || {}); +} - function get(key) { - return util.getMessageProperty(data,key); - }; - function set(key, value) { - util.setMessageProperty(data,key,value); - }; - function keys() { - var keysData = Object.keys(data); - if (seed == null) { - return keysData; - } else { - return keysData.filter(function (key) { - return key !== "set" && key !== "get" && key !== "keys"; - }); +function load() { + // load & init plugins in settings.contextStorage + var plugins = settings.contextStorage; + var alias = null; + if (plugins) { + for(var pluginName in plugins){ + if(pluginName === "_"){ + continue; + } + if(pluginName === "default" && typeof plugins[pluginName] === "string"){ + alias = plugins[pluginName]; + continue; + } + var plugin; + if(plugins[pluginName].hasOwnProperty("module")){ + var config = plugins[pluginName].config || {}; + copySettings(config, settings); + try{ + plugin = require("./"+plugins[pluginName].module); + }catch(err){ + throw new Error(plugins[pluginName].module + " could not be loaded"); + } + plugin.init(config); + externalContexts[pluginName] = plugin; + }else{ + throw new Error("module is is not defined in settings.contextStorage." + plugins[pluginName] ); + } } - }; - - obj.get = function(key) { - if(externalContext.canUse(key)) { - return externalContext.get(key, flowId); - }else{ - return get(key); - } - }; - obj.set = function(key, value) { - if(externalContext.canUse(key)) { - externalContext.set(key, value, flowId); - }else{ - set(key, value); + if(alias){ + if(externalContexts.hasOwnProperty(alias)){ + externalContexts["default"] = externalContexts[alias]; + }else{ + throw new Error("default is invalid" + plugins["default"]) + } } } - obj.keys = function(key) { - if(externalContext.canUse(key)) { - return externalContext.keys(key, flowId); - }else{ - return keys(); +} + +function copySettings(config, settings){ + var copy = ["userDir"] + config.settings = {}; + copy.forEach(function(setting){ + config.settings[setting] = clone(settings[setting]); + }); +} + +function createContext(id,seed) { + var scope = id; + var obj = seed || {}; + + obj.get = function(key) { + var result = parseKey(key); + if(!result){ + return externalContexts["_"].get(key, scope); } + if(externalContexts.hasOwnProperty(result[0])){ + return externalContexts[result[0]].get(result[1], scope); + }else if(externalContexts.hasOwnProperty("defalut")){ + return externalContexts["defalut"].get(result[1], scope); + }else{ + throw new Error(result[0] + " is not defined in setting.js"); + } + }; + obj.set = function(key, value) { + var result = parseKey(key); + if(!result){ + return externalContexts["_"].set(key, value, scope); + } + if(externalContexts.hasOwnProperty(result[0])){ + externalContexts[result[0]].set(result[1], value, scope); + }else if(externalContexts.hasOwnProperty("defalut")){ + externalContexts["defalut"].set(result[1], value, scope); + }else{ + throw new Error(result[0] + " is not defined in setting.js"); + } + }; + obj.keys = function() { + //TODO: discuss about keys() behavior + var keys = []; + for(var plugin in externalContexts){ + keys.concat(externalContexts[plugin].keys(scope)); + } + return keys; + }; + if(id === "global"){ + externalContexts["_"].setGlobalContext(seed); } return obj; } @@ -72,7 +125,7 @@ function createContext(id,seed) { function getContext(localId,flowId) { var contextId = localId; if (flowId) { - contextId = localId+"_"+flowId; + contextId = localId+":"+flowId; } if (contexts.hasOwnProperty(contextId)) { return contexts[contextId]; @@ -91,7 +144,10 @@ function getContext(localId,flowId) { function deleteContext(id,flowId) { var contextId = id; if (flowId) { - contextId = id+"_"+flowId; + contextId = id+":"+flowId; + } + for(var plugin in externalContexts){ + externalContexts[plugin].delete(contextId); } delete contexts[contextId]; } @@ -102,19 +158,36 @@ function clean(flowConfig) { var node; for (var id in contexts) { if (contexts.hasOwnProperty(id)) { - var idParts = id.split("_"); + var idParts = id.split(":"); if (!flowConfig.allNodes.hasOwnProperty(idParts[0])) { + for(var plugin in externalContexts){ + externalContexts[plugin].delete(id); + } delete contexts[id]; } } } } +function parseKey(key){ + var keys = null; + if(!key){ + return null; + } + var index_$ = key.indexOf("$"); + var index_dot = key.indexOf(".", 1); + if(index_$ === 0 && index_dot){ + keys = []; + keys[0] = key.substring(1,index_dot); + keys[1] = key.substring(index_dot + 1); + keys[0] = keys[0] || "default"; + } + return keys; +} + module.exports = { - init: function(settings) { - globalContext = createContext("global",settings.functionGlobalContext || {}); - externalContext.init(settings); - }, + init: init, + load: load, get: getContext, delete: deleteContext, clean:clean diff --git a/red/runtime/nodes/context/localfilesystem.js b/red/runtime/nodes/context/localfilesystem.js index 733c619d1..e77954189 100644 --- a/red/runtime/nodes/context/localfilesystem.js +++ b/red/runtime/nodes/context/localfilesystem.js @@ -14,67 +14,105 @@ * limitations under the License. **/ -var storage = require('node-persist'); +var JsonDB = require('node-json-db'); var fs = require('fs-extra'); -var fspath = require("path"); +var path = require("path"); var configs; -var storagePath; -var fileStorages; +var storageBaseDir; +var storages; -function createStorage(path) { - var fileStorage = storage.create({dir: fspath.join(storagePath,path)}); - fileStorage.initSync(); - fileStorages[path] = fileStorage; +function createStorage(scope) { + var i = scope.indexOf(":") + + if(i === -1){ + if(scope === "global"){ + storages[scope] = new JsonDB(path.join(storageBaseDir,"global",scope), true, true); + }else{ // scope:flow + storages[scope] = new JsonDB(path.join(storageBaseDir,scope,"flow"), true, true); + } + }else{ // scope:local + var ids = scope.split(":") + storages[scope] = new JsonDB(path.join(storageBaseDir,ids[1],ids[0]), true, true); + } } var localfilesystem = { init: function(_configs) { configs = _configs; - fileStorages = {}; + storages = {}; if (!configs.dir) { - try { - fs.statSync(fspath.join(process.env.NODE_RED_HOME,".config.json")); - storagePath = fspath.join(process.env.NODE_RED_HOME,"contexts"); - } catch(err) { + if(configs.settings && configs.settings.userDir){ + storageBaseDir = path.join(configs.settings.userDir,"contexts"); + }else{ try { - // Consider compatibility for older versions - if (process.env.HOMEPATH) { - fs.statSync(fspath.join(process.env.HOMEPATH,".node-red",".config.json")); - storagePath = fspath.join(process.env.HOMEPATH,".node-red","contexts"); - } + fs.statSync(path.join(process.env.NODE_RED_HOME,".config.json")); + storageBaseDir = path.join(process.env.NODE_RED_HOME,"contexts"); } catch(err) { - } - if (!configs.dir) { - storagePath = fspath.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || process.env.NODE_RED_HOME,".node-red","contexts"); + try { + // Consider compatibility for older versions + if (process.env.HOMEPATH) { + fs.statSync(path.join(process.env.HOMEPATH,".node-red",".config.json")); + storageBaseDir = path.join(process.env.HOMEPATH,".node-red","contexts"); + } + } catch(err) { + } + if (!storageBaseDir) { + storageBaseDir = path.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || process.env.NODE_RED_HOME,".node-red","contexts"); + } } } }else{ - storagePath = configs.dir; + storageBaseDir = configs.dir; } - }, - stop: function() { - return when.resolve(); - }, - get: function (key, flowId) { - if(!fileStorages[flowId]){ - createStorage(flowId); + get: function (key, scope) { + if(!storages[scope]){ + createStorage(scope); + } + try{ + storages[scope].reload(); + return storages[scope].getData("/" + key.replace(/\./g,"/")); + }catch(err){ + if(err.name === "DataError"){ + return undefined; + }else{ + throw err; + } } - return fileStorages[flowId].getItemSync(key); }, - set: function (key, value, flowId) { - if(!fileStorages[flowId]){ - createStorage(flowId); + set: function (key, value, scope) { + if(!storages[scope]){ + createStorage(scope); + } + if(value){ + storages[scope].push("/" + key.replace(/\./g,"/"), value); + }else{ + storages[scope].delete("/" + key.replace(/\./g,"/")); } - fileStorages[flowId].setItemSync(key, value); }, - keys: function (flowId) { - if(!fileStorages[flowId]){ - createStorage(flowId); + keys: function (scope) { + if(!storages[scope]){ + return []; + } + return Object.keys(storages[scope].getData("/")); + }, + delete: function(scope){ + if(storages[scope]){ + storages[scope].delete("/"); + if(scope.indexOf(":") === -1){ + fs.removeSync(path.dirname(storages[scope].filename)); + }else{ + try{ + fs.statSync(storages[scope].filename); + fs.unlinkSync(storages[scope].filename); + }catch(err){ + console.log("deleted"); + } + } + delete storages[scope]; } - return fileStorages[flowId].keys(); } }; diff --git a/red/runtime/nodes/context/memory.js b/red/runtime/nodes/context/memory.js new file mode 100644 index 000000000..deed13d36 --- /dev/null +++ b/red/runtime/nodes/context/memory.js @@ -0,0 +1,59 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed 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. + **/ + +var util = require("../../util"); + +var data; +var seedFlg = false; + +var memory = { + init: function(config) { + data = {}; + }, + get: function(key, scope) { + if(!data[scope]){ + data[scope] = {}; + } + return util.getMessageProperty(data[scope],key); + }, + set: function(key, value, scope) { + if(!data[scope]){ + data[scope] = {}; + } + util.setMessageProperty(data[scope],key,value); + }, + keys: function(scope){ + if(!data[scope]){ + data[scope] = {}; + } + var keysData = Object.keys(data[scope]); + if (scope !== "global") { + return keysData; + } else { + return keysData.filter(function (key) { + return key !== "set" && key !== "get" && key !== "keys"; + }); + } + }, + delete: function(scope){ + delete data[scope]; + }, + setGlobalContext: function(seed){ + data["global"] = seed; + } +}; + +module.exports = memory; \ No newline at end of file diff --git a/red/runtime/nodes/index.js b/red/runtime/nodes/index.js index b2f8ff3b5..139f62ffa 100644 --- a/red/runtime/nodes/index.js +++ b/red/runtime/nodes/index.js @@ -216,5 +216,8 @@ module.exports = { setCredentialSecret: credentials.setKey, clearCredentials: credentials.clear, exportCredentials: credentials.export, - getCredentialKeyType: credentials.getKeyType + getCredentialKeyType: credentials.getKeyType, + + // Contexts + loadContextsPlugin: context.load }; diff --git a/test/red/runtime/nodes/context/external_spec.js b/test/red/runtime/nodes/context/external_spec.js deleted file mode 100644 index 726d1c666..000000000 --- a/test/red/runtime/nodes/context/external_spec.js +++ /dev/null @@ -1,222 +0,0 @@ -/** - * Copyright JS Foundation and other contributors, http://js.foundation - * - * Licensed 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. - **/ -var should = require("should"); -var sinon = require('sinon'); -var external = require("../../../../../red/runtime/nodes/context/external"); - -describe("external", function() { - var stubModule = { - module: { - init: function(){}, - get: function(){}, - set: function(){}, - keys: function(){}, - run: function(){}, - close: function(){} - }, - config: {} - }; - describe('#init()', function() { - it('should load bundle module as default', function() { - external.init({ - contextStorage:{ - default:{ - module: "./localfilesystem", - config:{} - } - }}); - external.hasContextStorage("default").should.be.true(); - }); - - it('should load bundle module as localfile', function() { - external.init({ - contextStorage:{ - localfile:{ - module: "./localfilesystem", - config:{} - } - }}); - external.hasContextStorage("localfile").should.be.true(); - }); - - it('should not load non-existent module', function() { - external.init({ - contextStorage:{ - default:{ - module: "non-existent-module", - config:{} - } - - }}) - external.hasContextStorage("default").should.be.false(); - }); - - it('should load multiple modules', function() { - external.init({ - contextStorage:{ - default:{ - module: "./localfilesystem", - config:{} - }, - test:{ - module: { - init: function() { - return true; - } - }, - config:{} - } - } - }); - external.hasContextStorage("default").should.be.true(); - external.hasContextStorage("test").should.be.true(); - }); - - it('should load multiple modules without non-existent module', function() { - external.init({ - contextStorage:{ - nonexist:{ - module: "non-existent-module", - config:{} - }, - default:{ - module: "./localfilesystem", - config:{} - }, - test:{ - module: { - init: function() { - return true; - } - }, - config:{} - } - } - }); - external.hasContextStorage("nonexist").should.be.false(); - external.hasContextStorage("default").should.be.true(); - external.hasContextStorage("test").should.be.true(); - }); - }); - - // describe('#get()', function() { - // }); - - // describe('#set()', function() { - // }); - - // describe('#keys()', function() { - // }); - - // describe('#hasContextStorage()', function() { - // }); - - describe('#canUse()', function() { - it('should return true if specified module is loaded', function() { - external.init({ - contextStorage:{ - localfilesystem:{ - module: {name:"test",init: function(){return true}}, - config: {} - } - } - }); - external.canUse("$localfilesystem").should.be.true(); - external.canUse("$localfilesystem.foo").should.be.true(); - }); - it('should return false if specified module is not loaded', function() { - external.init({ - contextStorage:{ - localfilesystem:{ - module: {name:"test",init: function(){return true}}, - config: {} - } - } - }); - external.canUse("$file").should.be.false(); - external.canUse("$file.foo").should.be.false(); - }); - it('should return true if specified module is not loaded but default module is loaded', function() { - external.init({ - contextStorage:{ - default:{ - module: {name:"test",init: function(){return true}}, - config: {} - } - } - }); - external.canUse("$file").should.be.true(); - external.canUse("$file.foo").should.be.true(); - }); - it('should return false if argument does not contain module name', function() { - external.init({ - contextStorage:{ - default:{ - module: {name:"test",init: function(){return true}}, - config: {} - } - } - }); - external.canUse("file").should.be.false(); - external.canUse("file.foo").should.be.false(); - }); - }); - - describe('#parseKey()', function() { - function returnModuleAndKey(input, expectedModule, expectedKey) { - var result = external.parseKey(input); - result[0].should.eql(expectedModule); - result[1].should.eql(expectedKey); - }; - - function returnModule(input, expectedModule) { - var result = external.parseKey(input); - result[0].should.eql(expectedModule); - should(result[1]).be.null(); - }; - - it('should retrun module and key', function() { - returnModuleAndKey("$test.aaa","test","aaa"); - returnModuleAndKey("$test.aaa.bbb","test","aaa.bbb"); - returnModuleAndKey("$1.234","1","234"); - returnModuleAndKey("$$test.foo","$test","foo"); - returnModuleAndKey("$test.$foo","test","$foo"); - returnModuleAndKey("$test.$foo.$bar","test","$foo.$bar"); - returnModuleAndKey("$test..foo","test",".foo"); - returnModuleAndKey("$test..","test","."); - }); - - it('should retrun only module', function() { - returnModule("$test","test",null); - returnModule("$1","1",null); - returnModule("$$test","$test",null); - returnModule("$test.","test.",null); - }); - - it('should retrun module as default', function() { - returnModuleAndKey("$default.foo","default","foo"); - returnModuleAndKey("$.foo","default","foo"); - returnModule("$default","default"); - returnModule("$","default"); - }); - - it('should retrun null', function() { - should(external.parseKey("test.aaa")).be.null(); - should(external.parseKey("test")).be.null(); - should(external.parseKey(null)).be.null(); - }); - }); -}); \ No newline at end of file diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index fce39bcf4..1bcea888d 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -18,6 +18,7 @@ var should = require("should"); var sinon = require('sinon'); var path = require('path'); var fs = require('fs-extra'); +var rewire = require("rewire"); var Context = require("../../../../../red/runtime/nodes/context/index"); describe('context', function() { @@ -147,6 +148,7 @@ describe('context', function() { var stubGet = sinon.stub(); var stubSet = sinon.stub(); var stubKeys = sinon.stub(); + var stubDelete = sinon.stub(); var contextStorage={ test:{ module: { @@ -156,6 +158,7 @@ describe('context', function() { get: stubGet, set: stubSet, keys: stubKeys, + delete: stubDelete }, config:{} } @@ -235,4 +238,52 @@ describe('context', function() { }); }); }); + + describe('#parseKey()', function() { + var parseKey = rewire("../../../../../red/runtime/nodes/context/index").__get__("parseKey"); + + function returnModuleAndKey(input, expectedModule, expectedKey) { + var result = parseKey(input); + result[0].should.eql(expectedModule); + result[1].should.eql(expectedKey); + }; + + function returnModule(input, expectedModule) { + var result = parseKey(input); + result[0].should.eql(expectedModule); + should(result[1]).be.null(); + }; + + it('should retrun module and key', function() { + returnModuleAndKey("$test.aaa","test","aaa"); + returnModuleAndKey("$test.aaa.bbb","test","aaa.bbb"); + returnModuleAndKey("$1.234","1","234"); + returnModuleAndKey("$$test.foo","$test","foo"); + returnModuleAndKey("$test.$foo","test","$foo"); + returnModuleAndKey("$test.$foo.$bar","test","$foo.$bar"); + returnModuleAndKey("$test..foo","test",".foo"); + returnModuleAndKey("$test..","test","."); + }); + + // it('should retrun only module', function() { + // returnModule("$test","test",null); + // returnModule("$1","1",null); + // returnModule("$$test","$test",null); + // returnModule("$test.","test.",null); + // }); + + it('should retrun module as default', function() { + returnModuleAndKey("$default.foo","default","foo"); + returnModuleAndKey("$.foo","default","foo"); + // returnModule("$default","default"); + // returnModule("$","default"); + }); + + it('should retrun null', function() { + should(parseKey("test.aaa")).be.null(); + should(parseKey("test")).be.null(); + should(parseKey(null)).be.null(); + }); + }); + }); diff --git a/test/red/runtime/nodes/context/localfilesystem_spec.js b/test/red/runtime/nodes/context/localfilesystem_spec.js index e69de29bb..d0d545413 100644 --- a/test/red/runtime/nodes/context/localfilesystem_spec.js +++ b/test/red/runtime/nodes/context/localfilesystem_spec.js @@ -0,0 +1,126 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed 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. + **/ + +var should = require('should'); +var fs = require('fs-extra'); +var path = require("path"); +var context = require('../../../../../red/runtime/nodes/context/localfilesystem'); + +var resourcesDir = path.resolve(path.join(__dirname,"..","resources","context")); + +describe('localfilesystem',function() { + + beforeEach(function() { + context.init({dir: resourcesDir}); + }); + + afterEach(function() { + context.delete("nodeX"); + context.delete("nodeY"); + }); + + after(function() { + fs.removeSync(resourcesDir); + }); + + describe('#get/set',function() { + it('should store property',function() { + should.not.exist(context.get("foo","nodeX")); + context.set("foo","test","nodeX"); + context.get("foo","nodeX").should.eql("test"); + }); + + it('should store property - creates parent properties',function() { + context.set("foo.bar","test","nodeX"); + context.get("foo","nodeX").should.eql({bar:"test"}); + }); + + it('should delete property',function() { + context.set("foo.abc.bar1","test1","nodeX"); + context.set("foo.abc.bar2","test2","nodeX"); + context.get("foo.abc","nodeX").should.eql({bar1:"test1",bar2:"test2"}); + context.set("foo.abc.bar1",undefined,"nodeX"); + context.get("foo.abc","nodeX").should.eql({bar2:"test2"}); + context.set("foo.abc",undefined,"nodeX"); + should.not.exist(context.get("foo.abc","nodeX")); + context.set("foo",undefined,"nodeX"); + should.not.exist(context.get("foo","nodeX")); + }); + + it('should not shared context with other scope', function() { + should.not.exist(context.get("foo","nodeX")); + should.not.exist(context.get("foo","nodeY")); + context.set("foo","testX","nodeX"); + context.set("foo","testY","nodeY"); + + context.get("foo","nodeX").should.eql("testX"); + context.get("foo","nodeY").should.eql("testY"); + }); + }); + + describe('#keys',function() { + it('should enumerate context keys', function() { + var keys = context.keys("nodeX"); + keys.should.be.an.Array(); + keys.should.be.empty(); + + context.set("foo","bar","nodeX"); + keys = context.keys("nodeX"); + keys.should.have.length(1); + keys[0].should.eql("foo"); + + context.set("abc.def","bar","nodeX"); + keys = context.keys("nodeX"); + keys.should.have.length(2); + keys[1].should.eql("abc"); + }); + + it('should enumerate context keys in each scopes', function() { + var keysX = context.keys("nodeX"); + keysX.should.be.an.Array(); + keysX.should.be.empty(); + + var keysY = context.keys("nodeY"); + keysY.should.be.an.Array(); + keysY.should.be.empty(); + + context.set("foo","bar","nodeX"); + context.set("hoge","piyo","nodeY"); + keysX = context.keys("nodeX"); + keysX.should.have.length(1); + keysX[0].should.eql("foo"); + + keysY = context.keys("nodeY"); + keysY.should.have.length(1); + keysY[0].should.eql("hoge"); + }); + }); + + describe('#delete',function() { + it('should delete context',function() { + should.not.exist(context.get("foo","nodeX")); + should.not.exist(context.get("foo","nodeY")); + context.set("foo","abc","nodeX"); + context.set("foo","abc","nodeY"); + context.get("foo","nodeX").should.eql("abc"); + context.get("foo","nodeY").should.eql("abc"); + + context.delete("nodeX"); + should.not.exist(context.get("foo","nodeX")); + should.exist(context.get("foo","nodeY")); + }); + }); +}); \ No newline at end of file diff --git a/test/red/runtime/nodes/context/memory_spec.js b/test/red/runtime/nodes/context/memory_spec.js new file mode 100644 index 000000000..59acd67e1 --- /dev/null +++ b/test/red/runtime/nodes/context/memory_spec.js @@ -0,0 +1,141 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed 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. + **/ + +var should = require('should'); +var context = require('../../../../../red/runtime/nodes/context/memory'); + +describe('memory',function() { + + beforeEach(function() { + context.init({}); + }); + + describe('#get/set',function() { + it('should store property',function() { + should.not.exist(context.get("foo","nodeX")); + context.set("foo","test","nodeX"); + context.get("foo","nodeX").should.eql("test"); + }); + + it('should store property - creates parent properties',function() { + context.set("foo.bar","test","nodeX"); + context.get("foo","nodeX").should.eql({bar:"test"}); + }); + + it('should delete property',function() { + context.set("foo.abc.bar1","test1","nodeX"); + context.set("foo.abc.bar2","test2","nodeX"); + context.get("foo.abc","nodeX").should.eql({bar1:"test1",bar2:"test2"}); + context.set("foo.abc.bar1",undefined,"nodeX"); + context.get("foo.abc","nodeX").should.eql({bar2:"test2"}); + context.set("foo.abc",undefined,"nodeX"); + should.not.exist(context.get("foo.abc","nodeX")); + context.set("foo",undefined,"nodeX"); + should.not.exist(context.get("foo","nodeX")); + }); + + it('should not shared context with other scope', function() { + should.not.exist(context.get("foo","nodeX")); + should.not.exist(context.get("foo","nodeY")); + context.set("foo","testX","nodeX"); + context.set("foo","testY","nodeY"); + + context.get("foo","nodeX").should.eql("testX"); + context.get("foo","nodeY").should.eql("testY"); + }); + }); + + describe('#keys',function() { + it('should enumerate context keys', function() { + var keys = context.keys("nodeX"); + keys.should.be.an.Array(); + keys.should.be.empty(); + + context.set("foo","bar","nodeX"); + keys = context.keys("nodeX"); + keys.should.have.length(1); + keys[0].should.eql("foo"); + + context.set("abc.def","bar","nodeX"); + keys = context.keys("nodeX"); + keys.should.have.length(2); + keys[1].should.eql("abc"); + }); + + it('should enumerate context keys in each scopes', function() { + var keysX = context.keys("nodeX"); + keysX.should.be.an.Array(); + keysX.should.be.empty(); + + var keysY = context.keys("nodeY"); + keysY.should.be.an.Array(); + keysY.should.be.empty(); + + context.set("foo","bar","nodeX"); + context.set("hoge","piyo","nodeY"); + keysX = context.keys("nodeX"); + keysX.should.have.length(1); + keysX[0].should.eql("foo"); + + keysY = context.keys("nodeY"); + keysY.should.have.length(1); + keysY[0].should.eql("hoge"); + }); + + it('should enumerate only context keys when GlobalContext was given', function() { + var keys = context.keys("global"); + keys.should.be.an.Array(); + keys.should.be.empty(); + + var data = { + foo: "bar" + } + context.setGlobalContext(data); + keys = context.keys("global"); + keys.should.have.length(1); + keys[0].should.eql("foo"); + }); + }); + + describe('#delete',function() { + it('should delete context',function() { + should.not.exist(context.get("foo","nodeX")); + should.not.exist(context.get("foo","nodeY")); + context.set("foo","abc","nodeX"); + context.set("foo","abc","nodeY"); + context.get("foo","nodeX").should.eql("abc"); + context.get("foo","nodeY").should.eql("abc"); + + context.delete("nodeX"); + should.not.exist(context.get("foo","nodeX")); + should.exist(context.get("foo","nodeY")); + }); + }); + + describe('#setGlobalContext',function() { + it('should initialize global context with argument', function() { + var keys = context.keys("global"); + keys.should.be.an.Array(); + keys.should.be.empty(); + + var data = { + foo: "bar" + } + context.setGlobalContext(data); + context.get("foo","global").should.eql("bar"); + }); + }); +}); \ No newline at end of file From 3a476ac493fce8a84642bd8b0c38dc114903bbcc Mon Sep 17 00:00:00 2001 From: Kazuki-Nakanishi Date: Wed, 28 Mar 2018 15:54:16 +0900 Subject: [PATCH 07/32] Implemented error handlings --- red/runtime/nodes/context/index.js | 96 +++++---- test/red/runtime/nodes/context/index_spec.js | 211 ++++++++++++++----- 2 files changed, 209 insertions(+), 98 deletions(-) diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index 845408e8f..2a00d40d3 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -21,12 +21,13 @@ var settings; var contexts = {}; var globalContext = null; var externalContexts = {}; +var noContextStorage = false; function init(_settings) { settings = _settings; externalContexts = {}; - // init meomory plugin + // init memory plugin externalContexts["_"] = require("./memory"); externalContexts["_"].init(); globalContext = createContext("global",settings.functionGlobalContext || {}); @@ -37,6 +38,7 @@ function load() { var plugins = settings.contextStorage; var alias = null; if (plugins) { + noContextStorage = false; for(var pluginName in plugins){ if(pluginName === "_"){ continue; @@ -49,24 +51,30 @@ function load() { if(plugins[pluginName].hasOwnProperty("module")){ var config = plugins[pluginName].config || {}; copySettings(config, settings); - try{ - plugin = require("./"+plugins[pluginName].module); - }catch(err){ - throw new Error(plugins[pluginName].module + " could not be loaded"); + if(typeof plugins[pluginName].module === "string") { + try{ + plugin = require("./"+plugins[pluginName].module); + }catch(err){ + throw new Error(plugins[pluginName].module + " could not be loaded"); + } + } else { + plugin = plugins[pluginName].module; } plugin.init(config); externalContexts[pluginName] = plugin; }else{ - throw new Error("module is is not defined in settings.contextStorage." + plugins[pluginName] ); + throw new Error("module is not defined in settings.contextStorage." + pluginName ); } } if(alias){ if(externalContexts.hasOwnProperty(alias)){ externalContexts["default"] = externalContexts[alias]; }else{ - throw new Error("default is invalid" + plugins["default"]) + throw new Error("default is invalid. module name=" + plugins["default"]) } } + } else { + noContextStorage = true; } } @@ -78,35 +86,49 @@ function copySettings(config, settings){ }); } +function parseKey(key){ + if(!key){ + return null; + } + var keyPath = {storage: "", key: ""}; + var index_$ = key.indexOf("$"); + var index_dot = key.indexOf(".", 1); + if(index_$===0&&index_dot) { + keyPath.storage = key.substring(1,index_dot)||"default"; + keyPath.key = key.substring(index_dot+1); + } else { + keyPath.key = key; + } + return keyPath; +} + +function getContextStorage(keyPath) { + if (noContextStorage || !keyPath.storage) { + return externalContexts["_"]; + } else if (externalContexts.hasOwnProperty(keyPath.storage)) { + return externalContexts[keyPath.storage]; + } else if (externalContexts.hasOwnProperty("default")) { + return externalContexts["default"]; + } else { + var contextError = new Error(keyPath.storage + " is not defined in contextStorage on settings.js"); + contextError.name = "ContextError"; + throw contextError; + } +} + function createContext(id,seed) { var scope = id; var obj = seed || {}; obj.get = function(key) { - var result = parseKey(key); - if(!result){ - return externalContexts["_"].get(key, scope); - } - if(externalContexts.hasOwnProperty(result[0])){ - return externalContexts[result[0]].get(result[1], scope); - }else if(externalContexts.hasOwnProperty("defalut")){ - return externalContexts["defalut"].get(result[1], scope); - }else{ - throw new Error(result[0] + " is not defined in setting.js"); - } + var keyPath = parseKey(key); + var context = getContextStorage(keyPath); + return context.get(keyPath.key, scope); }; obj.set = function(key, value) { - var result = parseKey(key); - if(!result){ - return externalContexts["_"].set(key, value, scope); - } - if(externalContexts.hasOwnProperty(result[0])){ - externalContexts[result[0]].set(result[1], value, scope); - }else if(externalContexts.hasOwnProperty("defalut")){ - externalContexts["defalut"].set(result[1], value, scope); - }else{ - throw new Error(result[0] + " is not defined in setting.js"); - } + var keyPath = parseKey(key); + var context = getContextStorage(keyPath); + return context.set(keyPath.key, value, scope); }; obj.keys = function() { //TODO: discuss about keys() behavior @@ -169,22 +191,6 @@ function clean(flowConfig) { } } -function parseKey(key){ - var keys = null; - if(!key){ - return null; - } - var index_$ = key.indexOf("$"); - var index_dot = key.indexOf(".", 1); - if(index_$ === 0 && index_dot){ - keys = []; - keys[0] = key.substring(1,index_dot); - keys[1] = key.substring(index_dot + 1); - keys[0] = keys[0] || "default"; - } - return keys; -} - module.exports = { init: init, load: load, diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index 1bcea888d..e339de7e1 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -123,7 +123,7 @@ describe('context', function() { should.not.exist(context.get("foo")); }); - it('enumerates context keys', function() { + it.skip('enumerates context keys', function() { var context = Context.get("1","flowA"); var keys = context.keys(); @@ -150,24 +150,35 @@ describe('context', function() { var stubKeys = sinon.stub(); var stubDelete = sinon.stub(); var contextStorage={ - test:{ - module: { - init: function() { - return true; - }, - get: stubGet, - set: stubSet, - keys: stubKeys, - delete: stubDelete + test:{ + module: { + init: function() { + return true; }, - config:{} - } - }; + get: stubGet, + set: stubSet, + keys: stubKeys, + delete: stubDelete + }, + config:{} + } + }; + var contextDefaultStorage={ + default: "test", + test:{ + module: { + init: function() { + return true; + }, + get: stubGet, + set: stubSet, + keys: stubKeys, + delete: stubDelete + }, + config:{} + } + }; - beforeEach(function() { - Context.init({contextStorage:contextStorage}); - context = Context.get("1","flow"); - }); afterEach(function(done) { stubGet.reset(); stubSet.reset(); @@ -176,8 +187,15 @@ describe('context', function() { fs.remove(testDir,done); }); + function initializeContext() { + Context.init({contextStorage:contextStorage}); + Context.load(); + context = Context.get("1","flow"); + } + describe('if external context storage exists',function() { it('should store local property to external context storage',function() { + initializeContext(); should.not.exist(context.get("$test.foo")); context.set("$test.foo","test"); context.get("$test.foo"); @@ -187,6 +205,7 @@ describe('context', function() { stubKeys.called.should.be.true(); }); it('should store flow property to external context storage',function() { + initializeContext(); should.not.exist(context.flow.get("$test.foo")); context.flow.set("$test.foo","test"); context.flow.get("$test.foo"); @@ -196,6 +215,7 @@ describe('context', function() { stubKeys.called.should.be.true(); }); it('should store global property to external context storage',function() { + initializeContext(); should.not.exist(context.global.get("$test.foo")); context.global.set("$test.foo","test"); context.global.get("$test.foo"); @@ -204,37 +224,116 @@ describe('context', function() { stubSet.called.should.be.true(); stubKeys.called.should.be.true(); }); + it('should store data on default context', function() { + Context.init({contextStorage:contextDefaultStorage}); + Context.load(); + context = Context.get("1","flow"); + should.not.exist(context.get("$nonexist.foo")); + context.set("$nonexist.foo","test"); + context.get("$nonexist.foo"); + context.keys("$nonexist"); + stubGet.called.should.be.true(); + stubSet.called.should.be.true(); + stubKeys.called.should.be.true(); + }); + it('should load memory module', function(done) { + Context.init({ contextStorage: { _: {}}}); + try { + Context.load(); + context.set("_.foo","test"); + context.get("$_.foo"); + done(); + } catch (err) { + done(err); + } + }); + it('should load localfilesystem module', function(done) { + Context.init({contextStorage:{ file:{module:"localfilesystem"} }}); + try { + Context.load(); + done(); + } catch (err) { + done(err); + } + }); }); describe('if external context storage does not exist',function() { - it('should store local property to local memory',function() { - should.not.exist(context.flow.get("$nonexist.foo")); - context.set("$nonexist.foo","test"); - context.get("$nonexist.foo").should.eql("test"); - context.keys("$nonexist").should.have.length(1); - stubGet.notCalled.should.be.true(); - stubSet.notCalled.should.be.true(); - stubKeys.notCalled.should.be.true(); + it('should throw an error using undefined storage for local context', function(done) { + initializeContext(); + try { + context.get("$nonexist.local"); + should.fail(null, null, "An error was not thrown using undefined storage for local context"); + } catch (err) { + if (err.name === "ContextError") { + done(); + } else { + done(err); + } + } }); - - it('should store flow property to local memory',function() { - should.not.exist(context.flow.get("$nonexist.foo")); - context.flow.set("$nonexist.foo","test"); - context.flow.get("$nonexist.foo").should.eql("test"); - context.flow.keys("$nonexist").should.have.length(1); - stubGet.notCalled.should.be.true(); - stubSet.notCalled.should.be.true(); - stubKeys.notCalled.should.be.true(); + it('should throw an error using undefined storage for flow context', function(done) { + initializeContext(); + try { + context.flow.set("$nonexist.flow"); + should.fail(null, null, "An error was not thrown using undefined storage for flow context"); + } catch (err) { + if (err.name === "ContextError") { + done(); + } else { + done(err); + } + } }); - - it('should store global property to local memory',function() { - should.not.exist(context.global.get("$nonexist.foo")); - context.global.set("$nonexist.foo","test"); - context.global.get("$nonexist.foo").should.eql("test"); - context.global.keys("$nonexist").should.have.length(1); - stubGet.notCalled.should.be.true(); - stubSet.notCalled.should.be.true(); - stubKeys.notCalled.should.be.true(); + it('should fail when using invalid default context', function(done) { + Context.init({contextStorage:{default:"noexist"}}); + try { + Context.load(); + try { + should.fail(null, null, "An error was not thrown using undefined storage for flow context"); + } catch (err) { + done(err); + } + } catch (err) { + done(); + } + }); + it('should store data on memory when contextStorage is not defined', function() { + Context.init({}); + Context.load(); + context = Context.get("1","flow"); + context.set("$nonexist.key1", "val1"); + context.get("$nonexist.key1").should.eql("val1"); + context.flow.set("$nonexist.key2", "val2"); + context.flow.get("$nonexist.key2").should.eql("val2"); + context.global.set("$nonexist.key1", "val3"); + context.global.get("$nonexist.key1").should.eql("val3"); + }); + it('should fail for the storage with no module', function(done) { + Context.init({ contextStorage: { test: {}}}); + try { + Context.load(); + try { + should.fail(null, null, "Should fail when no module was specified"); + } catch (err) { + done(err); + } + } catch (err) { + done(); + } + }); + it('should fail to load non-existent module', function(done) { + Context.init({contextStorage:{ file:{module:"nonexistent"} }}); + try { + Context.load(); + try { + should.fail(null, null, "Should fail to load non-existent module"); + } catch (err) { + done(err); + } + } catch (err) { + done(); + } }); }); }); @@ -244,17 +343,17 @@ describe('context', function() { function returnModuleAndKey(input, expectedModule, expectedKey) { var result = parseKey(input); - result[0].should.eql(expectedModule); - result[1].should.eql(expectedKey); + result.storage.should.eql(expectedModule); + result.key.should.eql(expectedKey); }; function returnModule(input, expectedModule) { var result = parseKey(input); - result[0].should.eql(expectedModule); - should(result[1]).be.null(); + result.storage.should.eql(expectedModule); + should(result.key).be.null(); }; - it('should retrun module and key', function() { + it('should return module and key', function() { returnModuleAndKey("$test.aaa","test","aaa"); returnModuleAndKey("$test.aaa.bbb","test","aaa.bbb"); returnModuleAndKey("$1.234","1","234"); @@ -265,23 +364,29 @@ describe('context', function() { returnModuleAndKey("$test..","test","."); }); - // it('should retrun only module', function() { + // it('should return only module', function() { // returnModule("$test","test",null); // returnModule("$1","1",null); // returnModule("$$test","$test",null); // returnModule("$test.","test.",null); // }); - it('should retrun module as default', function() { + it('should return module as default', function() { returnModuleAndKey("$default.foo","default","foo"); returnModuleAndKey("$.foo","default","foo"); // returnModule("$default","default"); // returnModule("$","default"); }); - it('should retrun null', function() { - should(parseKey("test.aaa")).be.null(); - should(parseKey("test")).be.null(); + it('should return null', function() { + var keyPath = parseKey("test.aaa"); + keyPath.storage.should.eql(""); + keyPath.key.should.eql("test.aaa"); + + keyPath = parseKey("test"); + keyPath.storage.should.eql(""); + keyPath.key.should.eql("test"); + should(parseKey(null)).be.null(); }); }); From e046fc1ac5dc247577728c84f1380e762ac9f951 Mon Sep 17 00:00:00 2001 From: Kazuki-Nakanishi Date: Fri, 20 Apr 2018 11:41:29 +0900 Subject: [PATCH 08/32] Refactor parseKey and implement parseStorage --- red/runtime/locales/en-US/runtime.json | 11 + red/runtime/nodes/context/index.js | 70 +++--- test/red/runtime/nodes/context/index_spec.js | 225 +++++++++++++------ 3 files changed, 210 insertions(+), 96 deletions(-) diff --git a/red/runtime/locales/en-US/runtime.json b/red/runtime/locales/en-US/runtime.json index 6e9f057ae..f7a4b8a3d 100644 --- a/red/runtime/locales/en-US/runtime.json +++ b/red/runtime/locales/en-US/runtime.json @@ -155,5 +155,16 @@ "readme": "### About\n\nThis is your project's README.md file. It helps users understand what your\nproject does, how to use it and anything else they may need to know." } } + }, + + "context": { + "error-module-not-loaded": "'__module__' could not be loaded", + "error-module-not-defined": "'module' is not defined in '__storage__' of settings.contextStorage", + "error-invalid-default-module": "Invalid storage '__storage__' is specified as a default storage", + "error-key-zero-length": "Invalid property expression: zero-length", + "error-unexpected-space-character": "Invalid property expression: unexpected ' ' at position __index__", + "error-empty-key": "Invalid property expression: key is empty", + "error-use-undefined-storage": "Undefined storage '__storage__' is specified" } + } diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index 2a00d40d3..31721e659 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -16,6 +16,7 @@ var clone = require("clone"); var util = require("../../util"); +var log = require("../../log"); var settings; var contexts = {}; @@ -55,7 +56,7 @@ function load() { try{ plugin = require("./"+plugins[pluginName].module); }catch(err){ - throw new Error(plugins[pluginName].module + " could not be loaded"); + throw new Error(log._("context.error-module-not-loaded", {module:plugins[pluginName].module})); } } else { plugin = plugins[pluginName].module; @@ -63,14 +64,14 @@ function load() { plugin.init(config); externalContexts[pluginName] = plugin; }else{ - throw new Error("module is not defined in settings.contextStorage." + pluginName ); + throw new Error(log._("context.error-module-not-defined", {storage:pluginName})); } } if(alias){ if(externalContexts.hasOwnProperty(alias)){ externalContexts["default"] = externalContexts[alias]; }else{ - throw new Error("default is invalid. module name=" + plugins["default"]) + throw new Error(log._("context.error-invalid-default-module", {storage:plugins["default"]})); } } } else { @@ -86,31 +87,52 @@ function copySettings(config, settings){ }); } -function parseKey(key){ - if(!key){ - return null; +function parseStorage(key) { + if (!key || key.charAt(0) !== '$') { + return ""; + } else { + var endOfStorageName = key.indexOf("."); + if (endOfStorageName == -1) { + endOfStorageName = key.length; + } + return key.substring(1,endOfStorageName)||"default"; } - var keyPath = {storage: "", key: ""}; - var index_$ = key.indexOf("$"); - var index_dot = key.indexOf(".", 1); - if(index_$===0&&index_dot) { - keyPath.storage = key.substring(1,index_dot)||"default"; - keyPath.key = key.substring(index_dot+1); +} + +function parseKey(key) { + if (!key) { + throw new Error(log._("context.error-key-zero-length")); + } + var indexSpace = key.indexOf(" "); + if (indexSpace != -1) { + throw new Error(log._("context.error-unexpected-space-character", {index:indexSpace})); + } + var keyPath = { storage: "", key: "" }; + var indexDot = key.indexOf("."); + // The key of "$file" should be treated as a key without persistable context. + if (indexDot != -1) { + keyPath.storage = parseStorage(key); + } + if (keyPath.storage) { + keyPath.key = key.substring(indexDot + 1); } else { keyPath.key = key; } + if(!keyPath.key) { + throw new Error(log._("context.error-empty-key")); + } return keyPath; } -function getContextStorage(keyPath) { - if (noContextStorage || !keyPath.storage) { +function getContextStorage(storage) { + if (noContextStorage || !storage) { return externalContexts["_"]; - } else if (externalContexts.hasOwnProperty(keyPath.storage)) { - return externalContexts[keyPath.storage]; + } else if (externalContexts.hasOwnProperty(storage)) { + return externalContexts[storage]; } else if (externalContexts.hasOwnProperty("default")) { return externalContexts["default"]; } else { - var contextError = new Error(keyPath.storage + " is not defined in contextStorage on settings.js"); + var contextError = new Error(log._("context.error-use-undefined-storage", {storage:storage})); contextError.name = "ContextError"; throw contextError; } @@ -122,21 +144,19 @@ function createContext(id,seed) { obj.get = function(key) { var keyPath = parseKey(key); - var context = getContextStorage(keyPath); + var context = getContextStorage(keyPath.storage); return context.get(keyPath.key, scope); }; obj.set = function(key, value) { var keyPath = parseKey(key); - var context = getContextStorage(keyPath); + var context = getContextStorage(keyPath.storage); return context.set(keyPath.key, value, scope); }; - obj.keys = function() { + obj.keys = function(storage) { //TODO: discuss about keys() behavior - var keys = []; - for(var plugin in externalContexts){ - keys.concat(externalContexts[plugin].keys(scope)); - } - return keys; + var storageName = parseStorage(storage); + var context = getContextStorage(storageName); + return context.keys(scope); }; if(id === "global"){ externalContexts["_"].setGlobalContext(seed); diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index e339de7e1..bc9e6aff6 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -163,21 +163,6 @@ describe('context', function() { config:{} } }; - var contextDefaultStorage={ - default: "test", - test:{ - module: { - init: function() { - return true; - }, - get: stubGet, - set: stubSet, - keys: stubKeys, - delete: stubDelete - }, - config:{} - } - }; afterEach(function(done) { stubGet.reset(); @@ -193,7 +178,68 @@ describe('context', function() { context = Context.get("1","flow"); } + describe('key name',function() { + var memoryStorage = { + memory: { + module: "memory" + } + }; + beforeEach(function() { + Context.init({contextStorage:memoryStorage}); + Context.load(); + context = Context.get("1","flow"); + }); + afterEach(function() { + Context.clean({allNodes:{}}); + }); + it('should work correctly with the valid key name',function() { + context.set("$memory.azAZ09$_","valid"); + context.get("$memory.azAZ09$_").should.eql("valid"); + context.set("$memory.a.b","ab"); + context.get("$memory.a.b").should.eql("ab"); + }); + it('should treat the key name without dot as a normal context',function() { + context.set("$memory","normal"); + context.get("$memory").should.eql("normal"); + }); + it('should fail when specifying invalid characters',function() { + (function() { + context.set("$memory.a.-","invalid1"); + }).should.throw(); + (function() { + context.set("$memory.'abc","invalid2"); + }).should.throw(); + }); + it('should fail when specifying unnecesary space characters for key name',function() { + (function() { + context.set("$ memory.space","space1"); + }).should.throw(); + (function() { + context.set("$memory .space","space2"); + }).should.throw(); + (function() { + context.set("$memory. space","space3"); + }).should.throw(); + }); + }); + describe('if external context storage exists',function() { + var contextDefaultStorage={ + default: "test", + test:{ + module: { + init: function() { + return true; + }, + get: stubGet, + set: stubSet, + keys: stubKeys, + delete: stubDelete + }, + config:{} + } + }; + it('should store local property to external context storage',function() { initializeContext(); should.not.exist(context.get("$test.foo")); @@ -224,7 +270,7 @@ describe('context', function() { stubSet.called.should.be.true(); stubKeys.called.should.be.true(); }); - it('should store data on default context', function() { + it('should store data when non-existent context storage was specified', function() { Context.init({contextStorage:contextDefaultStorage}); Context.load(); context = Context.get("1","flow"); @@ -236,12 +282,39 @@ describe('context', function() { stubSet.called.should.be.true(); stubKeys.called.should.be.true(); }); + it('should use the default context', function() { + Context.init({contextStorage:contextDefaultStorage}); + Context.load(); + context = Context.get("1","flow"); + should.not.exist(context.get("$default.foo")); + context.set("$default.foo","default"); + context.get("$default.foo"); + context.keys("$default"); + stubGet.called.should.be.true(); + stubSet.called.should.be.true(); + stubKeys.called.should.be.true(); + }); + it('should use the alias of default context', function() { + Context.init({contextStorage:contextDefaultStorage}); + Context.load(); + context = Context.get("1","flow"); + should.not.exist(context.get("$.foo")); + context.set("$.foo","alias"); + context.get("$.foo"); + context.keys("$"); + stubGet.called.should.be.true(); + stubSet.called.should.be.true(); + stubKeys.called.should.be.true(); + }); it('should load memory module', function(done) { Context.init({ contextStorage: { _: {}}}); try { Context.load(); - context.set("_.foo","test"); - context.get("$_.foo"); + context.set("$_.foo","mem1"); + context.get("$_.foo").should.eql("mem1"); + var keys = context.keys("$_"); + keys.should.have.length(1); + keys[0].should.eql("foo"); done(); } catch (err) { done(err); @@ -256,6 +329,27 @@ describe('context', function() { done(err); } }); + it('should accept special storage name', function(done) { + Context.init({ + contextStorage:{ + "#%&":{module:"memory"}, + \u3042:{module:"memory"}, + 1:{module:"localfilesystem"}, + } + }); + try { + Context.load(); + context.set("$#%&.sign","sign1"); + context.get("$#%&.sign").should.eql("sign1"); + context.set("$\u3042.file2","file2"); + context.get("$\u3042.file2").should.eql("file2"); + context.set("$1.num","num3"); + context.get("$1.num").should.eql("num3"); + done(); + } catch (err) { + done(err); + } + }); }); describe('if external context storage does not exist',function() { @@ -285,18 +379,11 @@ describe('context', function() { } } }); - it('should fail when using invalid default context', function(done) { + it('should fail when using invalid default context', function() { Context.init({contextStorage:{default:"noexist"}}); - try { + (function() { Context.load(); - try { - should.fail(null, null, "An error was not thrown using undefined storage for flow context"); - } catch (err) { - done(err); - } - } catch (err) { - done(); - } + }).should.throw(); }); it('should store data on memory when contextStorage is not defined', function() { Context.init({}); @@ -309,31 +396,17 @@ describe('context', function() { context.global.set("$nonexist.key1", "val3"); context.global.get("$nonexist.key1").should.eql("val3"); }); - it('should fail for the storage with no module', function(done) { + it('should fail for the storage with no module', function() { Context.init({ contextStorage: { test: {}}}); - try { + (function() { Context.load(); - try { - should.fail(null, null, "Should fail when no module was specified"); - } catch (err) { - done(err); - } - } catch (err) { - done(); - } + }).should.throw(); }); - it('should fail to load non-existent module', function(done) { + it('should fail to load non-existent module', function() { Context.init({contextStorage:{ file:{module:"nonexistent"} }}); - try { + (function() { Context.load(); - try { - should.fail(null, null, "Should fail to load non-existent module"); - } catch (err) { - done(err); - } - } catch (err) { - done(); - } + }).should.throw(); }); }); }); @@ -347,12 +420,6 @@ describe('context', function() { result.key.should.eql(expectedKey); }; - function returnModule(input, expectedModule) { - var result = parseKey(input); - result.storage.should.eql(expectedModule); - should(result.key).be.null(); - }; - it('should return module and key', function() { returnModuleAndKey("$test.aaa","test","aaa"); returnModuleAndKey("$test.aaa.bbb","test","aaa.bbb"); @@ -362,32 +429,48 @@ describe('context', function() { returnModuleAndKey("$test.$foo.$bar","test","$foo.$bar"); returnModuleAndKey("$test..foo","test",".foo"); returnModuleAndKey("$test..","test","."); + returnModuleAndKey("$te-_st.aaa","te-_st","aaa"); + returnModuleAndKey("$te{st.a2","te{st","a2"); + returnModuleAndKey("$te[st.a3","te[st","a3"); + returnModuleAndKey("$te'st.a4","te'st","a4"); + returnModuleAndKey("$te\"st.a5","te\"st","a5"); }); - // it('should return only module', function() { - // returnModule("$test","test",null); - // returnModule("$1","1",null); - // returnModule("$$test","$test",null); - // returnModule("$test.","test.",null); - // }); - it('should return module as default', function() { returnModuleAndKey("$default.foo","default","foo"); returnModuleAndKey("$.foo","default","foo"); - // returnModule("$default","default"); - // returnModule("$","default"); }); - it('should return null', function() { - var keyPath = parseKey("test.aaa"); - keyPath.storage.should.eql(""); - keyPath.key.should.eql("test.aaa"); + it('should return only keys', function() { + returnModuleAndKey("test.aaa", "", "test.aaa"); + returnModuleAndKey("test", "", "test"); + returnModuleAndKey("$test", "", "$test"); + }); - keyPath = parseKey("test"); - keyPath.storage.should.eql(""); - keyPath.key.should.eql("test"); + it('should fail with null key', function() { + (function() { + parseKey(""); + }).should.throw(); - should(parseKey(null)).be.null(); + (function() { + parseKey(null); + }).should.throw(); + }); + + it('should fail with space character', function() { + (function() { + parseKey(" $test"); + }).should.throw(); + + (function() { + parseKey("$test .a"); + }).should.throw(); + }); + + it('should fail with empty key', function() { + (function() { + parseKey("$test."); + }).should.throw(); }); }); From 0be9c88106d3665ea82fe9ace2e0237d16281ade Mon Sep 17 00:00:00 2001 From: Hiroki Uchikawa Date: Mon, 23 Apr 2018 10:45:30 +0900 Subject: [PATCH 09/32] Improve processing when default is an alias and fix test cases --- red/runtime/nodes/context/index.js | 10 +++++----- test/red/runtime/index_spec.js | 3 +++ test/red/runtime/nodes/context/index_spec.js | 5 +++-- test/red/runtime/util_spec.js | 8 +++----- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index 31721e659..a0821531e 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -37,7 +37,7 @@ function init(_settings) { function load() { // load & init plugins in settings.contextStorage var plugins = settings.contextStorage; - var alias = null; + var isAlias = false; if (plugins) { noContextStorage = false; for(var pluginName in plugins){ @@ -45,7 +45,7 @@ function load() { continue; } if(pluginName === "default" && typeof plugins[pluginName] === "string"){ - alias = plugins[pluginName]; + isAlias = true; continue; } var plugin; @@ -67,9 +67,9 @@ function load() { throw new Error(log._("context.error-module-not-defined", {storage:pluginName})); } } - if(alias){ - if(externalContexts.hasOwnProperty(alias)){ - externalContexts["default"] = externalContexts[alias]; + if(isAlias){ + if(externalContexts.hasOwnProperty(plugins["default"])){ + externalContexts["default"] = externalContexts[plugins["default"]]; }else{ throw new Error(log._("context.error-invalid-default-module", {storage:plugins["default"]})); } diff --git a/test/red/runtime/index_spec.js b/test/red/runtime/index_spec.js index 4e30c1e8a..d67c12ee1 100644 --- a/test/red/runtime/index_spec.js +++ b/test/red/runtime/index_spec.js @@ -79,6 +79,7 @@ describe("runtime", function() { var redNodesGetNodeList; var redNodesLoadFlows; var redNodesStartFlows; + var redNodesLoadContextsPlugin; beforeEach(function() { storageInit = sinon.stub(storage,"init",function(settings) {return when.resolve();}); @@ -91,6 +92,7 @@ describe("runtime", function() { redNodesCleanModuleList = sinon.stub(redNodes,"cleanModuleList",function(){}); redNodesLoadFlows = sinon.stub(redNodes,"loadFlows",function() {return when.resolve()}); redNodesStartFlows = sinon.stub(redNodes,"startFlows",function() {}); + redNodesLoadContextsPlugin = sinon.stub(redNodes,"loadContextsPlugin",function() {}); }); afterEach(function() { storageInit.restore(); @@ -104,6 +106,7 @@ describe("runtime", function() { redNodesCleanModuleList.restore(); redNodesLoadFlows.restore(); redNodesStartFlows.restore(); + redNodesLoadContextsPlugin.restore(); }); it("reports errored/missing modules",function(done) { redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function(cb) { diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index bc9e6aff6..33fbd79b8 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -123,7 +123,7 @@ describe('context', function() { should.not.exist(context.get("foo")); }); - it.skip('enumerates context keys', function() { + it('enumerates context keys', function() { var context = Context.get("1","flowA"); var keys = context.keys(); @@ -168,6 +168,7 @@ describe('context', function() { stubGet.reset(); stubSet.reset(); stubKeys.reset(); + stubDelete.reset(); Context.clean({allNodes:{}}); fs.remove(testDir,done); }); @@ -418,7 +419,7 @@ describe('context', function() { var result = parseKey(input); result.storage.should.eql(expectedModule); result.key.should.eql(expectedKey); - }; + } it('should return module and key', function() { returnModuleAndKey("$test.aaa","test","aaa"); diff --git a/test/red/runtime/util_spec.js b/test/red/runtime/util_spec.js index 1b7438efd..c3e797d26 100644 --- a/test/red/runtime/util_spec.js +++ b/test/red/runtime/util_spec.js @@ -153,11 +153,9 @@ describe("red/util", function() { var v = util.getMessageProperty({a:"foo"},"msg.b"); should.not.exist(v); }); - it('should throw error if property parent does not exist', function() { - /*jshint immed: false */ - (function() { - util.getMessageProperty({a:"foo"},"msg.a.b.c"); - }).should.throw(); + it('should return undefined if property parent does not exist', function() { + var v = util.getMessageProperty({a:"foo"},"msg.a.b.c"); + should.not.exist(v); }); it('retrieves a property with array syntax', function() { var v = util.getMessageProperty({a:["foo","bar"]},"msg.a[0]"); From e30f8628db7f3f994407c219130255af44343142 Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Wed, 9 May 2018 19:19:37 +0900 Subject: [PATCH 10/32] Revert runtime/util --- red/runtime/util.js | 2 +- test/red/runtime/util_spec.js | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/red/runtime/util.js b/red/runtime/util.js index 4d077c21f..257e87777 100644 --- a/red/runtime/util.js +++ b/red/runtime/util.js @@ -240,7 +240,7 @@ function getMessageProperty(msg,expr) { var msgPropParts = normalisePropertyExpression(expr); var m; msgPropParts.reduce(function(obj, key) { - result = ((typeof obj !== "undefined") && (typeof obj[key] !== "undefined") ? obj[key] : undefined); + result = (typeof obj[key] !== "undefined" ? obj[key] : undefined); return result; }, msg); return result; diff --git a/test/red/runtime/util_spec.js b/test/red/runtime/util_spec.js index c3e797d26..1b7438efd 100644 --- a/test/red/runtime/util_spec.js +++ b/test/red/runtime/util_spec.js @@ -153,9 +153,11 @@ describe("red/util", function() { var v = util.getMessageProperty({a:"foo"},"msg.b"); should.not.exist(v); }); - it('should return undefined if property parent does not exist', function() { - var v = util.getMessageProperty({a:"foo"},"msg.a.b.c"); - should.not.exist(v); + it('should throw error if property parent does not exist', function() { + /*jshint immed: false */ + (function() { + util.getMessageProperty({a:"foo"},"msg.a.b.c"); + }).should.throw(); }); it('retrieves a property with array syntax', function() { var v = util.getMessageProperty({a:["foo","bar"]},"msg.a[0]"); From 84f598e14352a063940b1d6edaef67056febf090 Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Wed, 23 May 2018 10:23:39 +0900 Subject: [PATCH 11/32] Change prefix from $ to # --- red/runtime/nodes/context/index.js | 2 +- test/red/runtime/nodes/context/index_spec.js | 142 +++++++++---------- 2 files changed, 72 insertions(+), 72 deletions(-) diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index a0821531e..de34b3dc8 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -88,7 +88,7 @@ function copySettings(config, settings){ } function parseStorage(key) { - if (!key || key.charAt(0) !== '$') { + if (!key || key.charAt(0) !== '#') { return ""; } else { var endOfStorageName = key.indexOf("."); diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index 33fbd79b8..4b488657a 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -194,32 +194,32 @@ describe('context', function() { Context.clean({allNodes:{}}); }); it('should work correctly with the valid key name',function() { - context.set("$memory.azAZ09$_","valid"); - context.get("$memory.azAZ09$_").should.eql("valid"); - context.set("$memory.a.b","ab"); - context.get("$memory.a.b").should.eql("ab"); + context.set("#memory.azAZ09#_","valid"); + context.get("#memory.azAZ09#_").should.eql("valid"); + context.set("#memory.a.b","ab"); + context.get("#memory.a.b").should.eql("ab"); }); it('should treat the key name without dot as a normal context',function() { - context.set("$memory","normal"); - context.get("$memory").should.eql("normal"); + context.set("#memory","normal"); + context.get("#memory").should.eql("normal"); }); it('should fail when specifying invalid characters',function() { (function() { - context.set("$memory.a.-","invalid1"); + context.set("#memory.a.-","invalid1"); }).should.throw(); (function() { - context.set("$memory.'abc","invalid2"); + context.set("#memory.'abc","invalid2"); }).should.throw(); }); it('should fail when specifying unnecesary space characters for key name',function() { (function() { - context.set("$ memory.space","space1"); + context.set("# memory.space","space1"); }).should.throw(); (function() { - context.set("$memory .space","space2"); + context.set("#memory .space","space2"); }).should.throw(); (function() { - context.set("$memory. space","space3"); + context.set("#memory. space","space3"); }).should.throw(); }); }); @@ -243,30 +243,30 @@ describe('context', function() { it('should store local property to external context storage',function() { initializeContext(); - should.not.exist(context.get("$test.foo")); - context.set("$test.foo","test"); - context.get("$test.foo"); - context.keys("$test"); + should.not.exist(context.get("#test.foo")); + context.set("#test.foo","test"); + context.get("#test.foo"); + context.keys("#test"); stubGet.called.should.be.true(); stubSet.called.should.be.true(); stubKeys.called.should.be.true(); }); it('should store flow property to external context storage',function() { initializeContext(); - should.not.exist(context.flow.get("$test.foo")); - context.flow.set("$test.foo","test"); - context.flow.get("$test.foo"); - context.flow.keys("$test"); + should.not.exist(context.flow.get("#test.foo")); + context.flow.set("#test.foo","test"); + context.flow.get("#test.foo"); + context.flow.keys("#test"); stubGet.called.should.be.true(); stubSet.called.should.be.true(); stubKeys.called.should.be.true(); }); it('should store global property to external context storage',function() { initializeContext(); - should.not.exist(context.global.get("$test.foo")); - context.global.set("$test.foo","test"); - context.global.get("$test.foo"); - context.global.keys("$test"); + should.not.exist(context.global.get("#test.foo")); + context.global.set("#test.foo","test"); + context.global.get("#test.foo"); + context.global.keys("#test"); stubGet.called.should.be.true(); stubSet.called.should.be.true(); stubKeys.called.should.be.true(); @@ -275,10 +275,10 @@ describe('context', function() { Context.init({contextStorage:contextDefaultStorage}); Context.load(); context = Context.get("1","flow"); - should.not.exist(context.get("$nonexist.foo")); - context.set("$nonexist.foo","test"); - context.get("$nonexist.foo"); - context.keys("$nonexist"); + should.not.exist(context.get("#nonexist.foo")); + context.set("#nonexist.foo","test"); + context.get("#nonexist.foo"); + context.keys("#nonexist"); stubGet.called.should.be.true(); stubSet.called.should.be.true(); stubKeys.called.should.be.true(); @@ -287,10 +287,10 @@ describe('context', function() { Context.init({contextStorage:contextDefaultStorage}); Context.load(); context = Context.get("1","flow"); - should.not.exist(context.get("$default.foo")); - context.set("$default.foo","default"); - context.get("$default.foo"); - context.keys("$default"); + should.not.exist(context.get("#default.foo")); + context.set("#default.foo","default"); + context.get("#default.foo"); + context.keys("#default"); stubGet.called.should.be.true(); stubSet.called.should.be.true(); stubKeys.called.should.be.true(); @@ -299,10 +299,10 @@ describe('context', function() { Context.init({contextStorage:contextDefaultStorage}); Context.load(); context = Context.get("1","flow"); - should.not.exist(context.get("$.foo")); - context.set("$.foo","alias"); - context.get("$.foo"); - context.keys("$"); + should.not.exist(context.get("#.foo")); + context.set("#.foo","alias"); + context.get("#.foo"); + context.keys("#"); stubGet.called.should.be.true(); stubSet.called.should.be.true(); stubKeys.called.should.be.true(); @@ -311,9 +311,9 @@ describe('context', function() { Context.init({ contextStorage: { _: {}}}); try { Context.load(); - context.set("$_.foo","mem1"); - context.get("$_.foo").should.eql("mem1"); - var keys = context.keys("$_"); + context.set("#_.foo","mem1"); + context.get("#_.foo").should.eql("mem1"); + var keys = context.keys("#_"); keys.should.have.length(1); keys[0].should.eql("foo"); done(); @@ -340,12 +340,12 @@ describe('context', function() { }); try { Context.load(); - context.set("$#%&.sign","sign1"); - context.get("$#%&.sign").should.eql("sign1"); - context.set("$\u3042.file2","file2"); - context.get("$\u3042.file2").should.eql("file2"); - context.set("$1.num","num3"); - context.get("$1.num").should.eql("num3"); + context.set("##%&.sign","sign1"); + context.get("##%&.sign").should.eql("sign1"); + context.set("#\u3042.file2","file2"); + context.get("#\u3042.file2").should.eql("file2"); + context.set("#1.num","num3"); + context.get("#1.num").should.eql("num3"); done(); } catch (err) { done(err); @@ -357,7 +357,7 @@ describe('context', function() { it('should throw an error using undefined storage for local context', function(done) { initializeContext(); try { - context.get("$nonexist.local"); + context.get("#nonexist.local"); should.fail(null, null, "An error was not thrown using undefined storage for local context"); } catch (err) { if (err.name === "ContextError") { @@ -370,7 +370,7 @@ describe('context', function() { it('should throw an error using undefined storage for flow context', function(done) { initializeContext(); try { - context.flow.set("$nonexist.flow"); + context.flow.set("#nonexist.flow"); should.fail(null, null, "An error was not thrown using undefined storage for flow context"); } catch (err) { if (err.name === "ContextError") { @@ -390,12 +390,12 @@ describe('context', function() { Context.init({}); Context.load(); context = Context.get("1","flow"); - context.set("$nonexist.key1", "val1"); - context.get("$nonexist.key1").should.eql("val1"); - context.flow.set("$nonexist.key2", "val2"); - context.flow.get("$nonexist.key2").should.eql("val2"); - context.global.set("$nonexist.key1", "val3"); - context.global.get("$nonexist.key1").should.eql("val3"); + context.set("#nonexist.key1", "val1"); + context.get("#nonexist.key1").should.eql("val1"); + context.flow.set("#nonexist.key2", "val2"); + context.flow.get("#nonexist.key2").should.eql("val2"); + context.global.set("#nonexist.key1", "val3"); + context.global.get("#nonexist.key1").should.eql("val3"); }); it('should fail for the storage with no module', function() { Context.init({ contextStorage: { test: {}}}); @@ -422,30 +422,30 @@ describe('context', function() { } it('should return module and key', function() { - returnModuleAndKey("$test.aaa","test","aaa"); - returnModuleAndKey("$test.aaa.bbb","test","aaa.bbb"); - returnModuleAndKey("$1.234","1","234"); - returnModuleAndKey("$$test.foo","$test","foo"); - returnModuleAndKey("$test.$foo","test","$foo"); - returnModuleAndKey("$test.$foo.$bar","test","$foo.$bar"); - returnModuleAndKey("$test..foo","test",".foo"); - returnModuleAndKey("$test..","test","."); - returnModuleAndKey("$te-_st.aaa","te-_st","aaa"); - returnModuleAndKey("$te{st.a2","te{st","a2"); - returnModuleAndKey("$te[st.a3","te[st","a3"); - returnModuleAndKey("$te'st.a4","te'st","a4"); - returnModuleAndKey("$te\"st.a5","te\"st","a5"); + returnModuleAndKey("#test.aaa","test","aaa"); + returnModuleAndKey("#test.aaa.bbb","test","aaa.bbb"); + returnModuleAndKey("#1.234","1","234"); + returnModuleAndKey("##test.foo","#test","foo"); + returnModuleAndKey("#test.#foo","test","#foo"); + returnModuleAndKey("#test.#foo.#bar","test","#foo.#bar"); + returnModuleAndKey("#test..foo","test",".foo"); + returnModuleAndKey("#test..","test","."); + returnModuleAndKey("#te-_st.aaa","te-_st","aaa"); + returnModuleAndKey("#te{st.a2","te{st","a2"); + returnModuleAndKey("#te[st.a3","te[st","a3"); + returnModuleAndKey("#te'st.a4","te'st","a4"); + returnModuleAndKey("#te\"st.a5","te\"st","a5"); }); it('should return module as default', function() { - returnModuleAndKey("$default.foo","default","foo"); - returnModuleAndKey("$.foo","default","foo"); + returnModuleAndKey("#default.foo","default","foo"); + returnModuleAndKey("#.foo","default","foo"); }); it('should return only keys', function() { returnModuleAndKey("test.aaa", "", "test.aaa"); returnModuleAndKey("test", "", "test"); - returnModuleAndKey("$test", "", "$test"); + returnModuleAndKey("#test", "", "#test"); }); it('should fail with null key', function() { @@ -460,17 +460,17 @@ describe('context', function() { it('should fail with space character', function() { (function() { - parseKey(" $test"); + parseKey(" #test"); }).should.throw(); (function() { - parseKey("$test .a"); + parseKey("#test .a"); }).should.throw(); }); it('should fail with empty key', function() { (function() { - parseKey("$test."); + parseKey("#test."); }).should.throw(); }); }); From 7fafa21a1b3f61860c7262e650fc3084e1171096 Mon Sep 17 00:00:00 2001 From: Hiroki Uchikawa Date: Wed, 23 May 2018 11:42:53 +0900 Subject: [PATCH 12/32] Change the order of arguments --- red/runtime/nodes/context/index.js | 4 +- red/runtime/nodes/context/localfilesystem.js | 4 +- red/runtime/nodes/context/memory.js | 5 +- test/red/runtime/nodes/context/index_spec.js | 2 +- .../nodes/context/localfilesystem_spec.js | 64 +++++++++--------- test/red/runtime/nodes/context/memory_spec.js | 66 +++++++++---------- 6 files changed, 72 insertions(+), 73 deletions(-) diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index de34b3dc8..633dc22d5 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -145,12 +145,12 @@ function createContext(id,seed) { obj.get = function(key) { var keyPath = parseKey(key); var context = getContextStorage(keyPath.storage); - return context.get(keyPath.key, scope); + return context.get(scope, keyPath.key); }; obj.set = function(key, value) { var keyPath = parseKey(key); var context = getContextStorage(keyPath.storage); - return context.set(keyPath.key, value, scope); + return context.set(scope, keyPath.key, value); }; obj.keys = function(storage) { //TODO: discuss about keys() behavior diff --git a/red/runtime/nodes/context/localfilesystem.js b/red/runtime/nodes/context/localfilesystem.js index e77954189..30b7f5920 100644 --- a/red/runtime/nodes/context/localfilesystem.js +++ b/red/runtime/nodes/context/localfilesystem.js @@ -66,7 +66,7 @@ var localfilesystem = { storageBaseDir = configs.dir; } }, - get: function (key, scope) { + get: function (scope, key) { if(!storages[scope]){ createStorage(scope); } @@ -82,7 +82,7 @@ var localfilesystem = { } }, - set: function (key, value, scope) { + set: function (scope, key, value) { if(!storages[scope]){ createStorage(scope); } diff --git a/red/runtime/nodes/context/memory.js b/red/runtime/nodes/context/memory.js index deed13d36..820039747 100644 --- a/red/runtime/nodes/context/memory.js +++ b/red/runtime/nodes/context/memory.js @@ -17,19 +17,18 @@ var util = require("../../util"); var data; -var seedFlg = false; var memory = { init: function(config) { data = {}; }, - get: function(key, scope) { + get: function(scope, key) { if(!data[scope]){ data[scope] = {}; } return util.getMessageProperty(data[scope],key); }, - set: function(key, value, scope) { + set: function(scope, key, value) { if(!data[scope]){ data[scope] = {}; } diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index 4b488657a..56135e52a 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -335,7 +335,7 @@ describe('context', function() { contextStorage:{ "#%&":{module:"memory"}, \u3042:{module:"memory"}, - 1:{module:"localfilesystem"}, + 1:{module:"memory"}, } }); try { diff --git a/test/red/runtime/nodes/context/localfilesystem_spec.js b/test/red/runtime/nodes/context/localfilesystem_spec.js index d0d545413..8fc538c8d 100644 --- a/test/red/runtime/nodes/context/localfilesystem_spec.js +++ b/test/red/runtime/nodes/context/localfilesystem_spec.js @@ -38,36 +38,36 @@ describe('localfilesystem',function() { describe('#get/set',function() { it('should store property',function() { - should.not.exist(context.get("foo","nodeX")); - context.set("foo","test","nodeX"); - context.get("foo","nodeX").should.eql("test"); + should.not.exist(context.get("nodeX","foo")); + context.set("nodeX","foo","test"); + context.get("nodeX","foo").should.eql("test"); }); it('should store property - creates parent properties',function() { - context.set("foo.bar","test","nodeX"); - context.get("foo","nodeX").should.eql({bar:"test"}); + context.set("nodeX","foo.bar","test"); + context.get("nodeX","foo").should.eql({bar:"test"}); }); it('should delete property',function() { - context.set("foo.abc.bar1","test1","nodeX"); - context.set("foo.abc.bar2","test2","nodeX"); - context.get("foo.abc","nodeX").should.eql({bar1:"test1",bar2:"test2"}); - context.set("foo.abc.bar1",undefined,"nodeX"); - context.get("foo.abc","nodeX").should.eql({bar2:"test2"}); - context.set("foo.abc",undefined,"nodeX"); - should.not.exist(context.get("foo.abc","nodeX")); - context.set("foo",undefined,"nodeX"); - should.not.exist(context.get("foo","nodeX")); + context.set("nodeX","foo.abc.bar1","test1"); + context.set("nodeX","foo.abc.bar2","test2"); + context.get("nodeX","foo.abc").should.eql({bar1:"test1",bar2:"test2"}); + context.set("nodeX","foo.abc.bar1",undefined); + context.get("nodeX","foo.abc").should.eql({bar2:"test2"}); + context.set("nodeX","foo.abc",undefined); + should.not.exist(context.get("nodeX","foo.abc")); + context.set("nodeX","foo",undefined); + should.not.exist(context.get("nodeX","foo")); }); it('should not shared context with other scope', function() { - should.not.exist(context.get("foo","nodeX")); - should.not.exist(context.get("foo","nodeY")); - context.set("foo","testX","nodeX"); - context.set("foo","testY","nodeY"); + should.not.exist(context.get("nodeX","foo")); + should.not.exist(context.get("nodeY","foo")); + context.set("nodeX","foo","testX"); + context.set("nodeY","foo","testY"); - context.get("foo","nodeX").should.eql("testX"); - context.get("foo","nodeY").should.eql("testY"); + context.get("nodeX","foo").should.eql("testX"); + context.get("nodeY","foo").should.eql("testY"); }); }); @@ -77,12 +77,12 @@ describe('localfilesystem',function() { keys.should.be.an.Array(); keys.should.be.empty(); - context.set("foo","bar","nodeX"); + context.set("nodeX","foo","bar"); keys = context.keys("nodeX"); keys.should.have.length(1); keys[0].should.eql("foo"); - context.set("abc.def","bar","nodeX"); + context.set("nodeX","abc.def","bar"); keys = context.keys("nodeX"); keys.should.have.length(2); keys[1].should.eql("abc"); @@ -97,8 +97,8 @@ describe('localfilesystem',function() { keysY.should.be.an.Array(); keysY.should.be.empty(); - context.set("foo","bar","nodeX"); - context.set("hoge","piyo","nodeY"); + context.set("nodeX","foo","bar"); + context.set("nodeY","hoge","piyo"); keysX = context.keys("nodeX"); keysX.should.have.length(1); keysX[0].should.eql("foo"); @@ -111,16 +111,16 @@ describe('localfilesystem',function() { describe('#delete',function() { it('should delete context',function() { - should.not.exist(context.get("foo","nodeX")); - should.not.exist(context.get("foo","nodeY")); - context.set("foo","abc","nodeX"); - context.set("foo","abc","nodeY"); - context.get("foo","nodeX").should.eql("abc"); - context.get("foo","nodeY").should.eql("abc"); + should.not.exist(context.get("nodeX","foo")); + should.not.exist(context.get("nodeY","foo")); + context.set("nodeX","foo","abc"); + context.set("nodeY","foo","abc"); + context.get("nodeX","foo").should.eql("abc"); + context.get("nodeY","foo").should.eql("abc"); context.delete("nodeX"); - should.not.exist(context.get("foo","nodeX")); - should.exist(context.get("foo","nodeY")); + should.not.exist(context.get("nodeX","foo")); + should.exist(context.get("nodeY","foo")); }); }); }); \ No newline at end of file diff --git a/test/red/runtime/nodes/context/memory_spec.js b/test/red/runtime/nodes/context/memory_spec.js index 59acd67e1..91f4ab917 100644 --- a/test/red/runtime/nodes/context/memory_spec.js +++ b/test/red/runtime/nodes/context/memory_spec.js @@ -25,36 +25,36 @@ describe('memory',function() { describe('#get/set',function() { it('should store property',function() { - should.not.exist(context.get("foo","nodeX")); - context.set("foo","test","nodeX"); - context.get("foo","nodeX").should.eql("test"); + should.not.exist(context.get("nodeX","foo")); + context.set("nodeX","foo","test"); + context.get("nodeX","foo").should.eql("test"); }); it('should store property - creates parent properties',function() { - context.set("foo.bar","test","nodeX"); - context.get("foo","nodeX").should.eql({bar:"test"}); + context.set("nodeX","foo.bar","test"); + context.get("nodeX","foo").should.eql({bar:"test"}); }); it('should delete property',function() { - context.set("foo.abc.bar1","test1","nodeX"); - context.set("foo.abc.bar2","test2","nodeX"); - context.get("foo.abc","nodeX").should.eql({bar1:"test1",bar2:"test2"}); - context.set("foo.abc.bar1",undefined,"nodeX"); - context.get("foo.abc","nodeX").should.eql({bar2:"test2"}); - context.set("foo.abc",undefined,"nodeX"); - should.not.exist(context.get("foo.abc","nodeX")); - context.set("foo",undefined,"nodeX"); - should.not.exist(context.get("foo","nodeX")); + context.set("nodeX","foo.abc.bar1","test1"); + context.set("nodeX","foo.abc.bar2","test2"); + context.get("nodeX","foo.abc").should.eql({bar1:"test1",bar2:"test2"}); + context.set("nodeX","foo.abc.bar1",undefined); + context.get("nodeX","foo.abc").should.eql({bar2:"test2"}); + context.set("nodeX","foo.abc",undefined); + should.not.exist(context.get("nodeX","foo.abc")); + context.set("nodeX","foo",undefined); + should.not.exist(context.get("nodeX","foo")); }); it('should not shared context with other scope', function() { - should.not.exist(context.get("foo","nodeX")); - should.not.exist(context.get("foo","nodeY")); - context.set("foo","testX","nodeX"); - context.set("foo","testY","nodeY"); + should.not.exist(context.get("nodeX","foo")); + should.not.exist(context.get("nodeY","foo")); + context.set("nodeX","foo","testX"); + context.set("nodeY","foo","testY"); - context.get("foo","nodeX").should.eql("testX"); - context.get("foo","nodeY").should.eql("testY"); + context.get("nodeX","foo").should.eql("testX"); + context.get("nodeY","foo").should.eql("testY"); }); }); @@ -64,12 +64,12 @@ describe('memory',function() { keys.should.be.an.Array(); keys.should.be.empty(); - context.set("foo","bar","nodeX"); + context.set("nodeX","foo","bar"); keys = context.keys("nodeX"); keys.should.have.length(1); keys[0].should.eql("foo"); - context.set("abc.def","bar","nodeX"); + context.set("nodeX","abc.def","bar"); keys = context.keys("nodeX"); keys.should.have.length(2); keys[1].should.eql("abc"); @@ -84,8 +84,8 @@ describe('memory',function() { keysY.should.be.an.Array(); keysY.should.be.empty(); - context.set("foo","bar","nodeX"); - context.set("hoge","piyo","nodeY"); + context.set("nodeX","foo","bar"); + context.set("nodeY","hoge","piyo"); keysX = context.keys("nodeX"); keysX.should.have.length(1); keysX[0].should.eql("foo"); @@ -112,16 +112,16 @@ describe('memory',function() { describe('#delete',function() { it('should delete context',function() { - should.not.exist(context.get("foo","nodeX")); - should.not.exist(context.get("foo","nodeY")); - context.set("foo","abc","nodeX"); - context.set("foo","abc","nodeY"); - context.get("foo","nodeX").should.eql("abc"); - context.get("foo","nodeY").should.eql("abc"); + should.not.exist(context.get("nodeX","foo")); + should.not.exist(context.get("nodeY","foo")); + context.set("nodeX","foo","abc"); + context.set("nodeY","foo","abc"); + context.get("nodeX","foo").should.eql("abc"); + context.get("nodeY","foo").should.eql("abc"); context.delete("nodeX"); - should.not.exist(context.get("foo","nodeX")); - should.exist(context.get("foo","nodeY")); + should.not.exist(context.get("nodeX","foo")); + should.exist(context.get("nodeY","foo")); }); }); @@ -135,7 +135,7 @@ describe('memory',function() { foo: "bar" } context.setGlobalContext(data); - context.get("foo","global").should.eql("bar"); + context.get("global","foo").should.eql("bar"); }); }); }); \ No newline at end of file From 28d05e2449dd5e486d4f042e07f2301f534ce4a8 Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Thu, 24 May 2018 21:42:40 +0900 Subject: [PATCH 13/32] Allow multiple instances of a given storage module to exist --- red/runtime/nodes/context/index.js | 7 +- red/runtime/nodes/context/localfilesystem.js | 161 +++++++++--------- red/runtime/nodes/context/memory.js | 82 +++++---- test/red/runtime/nodes/context/index_spec.js | 30 ++-- .../nodes/context/localfilesystem_spec.js | 16 +- test/red/runtime/nodes/context/memory_spec.js | 5 +- 6 files changed, 157 insertions(+), 144 deletions(-) diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index 633dc22d5..30481e9d1 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -29,8 +29,8 @@ function init(_settings) { externalContexts = {}; // init memory plugin - externalContexts["_"] = require("./memory"); - externalContexts["_"].init(); + var memory = require("./memory"); + externalContexts["_"] = memory(); globalContext = createContext("global",settings.functionGlobalContext || {}); } @@ -61,8 +61,7 @@ function load() { } else { plugin = plugins[pluginName].module; } - plugin.init(config); - externalContexts[pluginName] = plugin; + externalContexts[pluginName] = plugin(config); }else{ throw new Error(log._("context.error-module-not-defined", {storage:pluginName})); } diff --git a/red/runtime/nodes/context/localfilesystem.js b/red/runtime/nodes/context/localfilesystem.js index 30b7f5920..d18f4fd76 100644 --- a/red/runtime/nodes/context/localfilesystem.js +++ b/red/runtime/nodes/context/localfilesystem.js @@ -18,102 +18,109 @@ var JsonDB = require('node-json-db'); var fs = require('fs-extra'); var path = require("path"); -var configs; -var storageBaseDir; -var storages; - -function createStorage(scope) { +function createStorage(storageBaseDir, scope) { var i = scope.indexOf(":") if(i === -1){ if(scope === "global"){ - storages[scope] = new JsonDB(path.join(storageBaseDir,"global",scope), true, true); + return new JsonDB(path.join(storageBaseDir,"global",scope), true, true); }else{ // scope:flow - storages[scope] = new JsonDB(path.join(storageBaseDir,scope,"flow"), true, true); + return new JsonDB(path.join(storageBaseDir,scope,"flow"), true, true); } }else{ // scope:local var ids = scope.split(":") - storages[scope] = new JsonDB(path.join(storageBaseDir,ids[1],ids[0]), true, true); + return new JsonDB(path.join(storageBaseDir,ids[1],ids[0]), true, true); } } -var localfilesystem = { - init: function(_configs) { - configs = _configs; - storages = {}; - if (!configs.dir) { - if(configs.settings && configs.settings.userDir){ - storageBaseDir = path.join(configs.settings.userDir,"contexts"); - }else{ +function getStoragePath(config) { + var base = config.base || "contexts"; + var storageBaseDir; + if (!config.dir) { + if(config.settings && config.settings.userDir){ + storageBaseDir = path.join(config.settings.userDir, base); + }else{ + try { + fs.statSync(path.join(process.env.NODE_RED_HOME,".config.json")); + storageBaseDir = path.join(process.env.NODE_RED_HOME, base); + } catch(err) { try { - fs.statSync(path.join(process.env.NODE_RED_HOME,".config.json")); - storageBaseDir = path.join(process.env.NODE_RED_HOME,"contexts"); + // Consider compatibility for older versions + if (process.env.HOMEPATH) { + fs.statSync(path.join(process.env.HOMEPATH,".node-red",".config.json")); + storageBaseDir = path.join(process.env.HOMEPATH, ".node-red", base); + } } catch(err) { - try { - // Consider compatibility for older versions - if (process.env.HOMEPATH) { - fs.statSync(path.join(process.env.HOMEPATH,".node-red",".config.json")); - storageBaseDir = path.join(process.env.HOMEPATH,".node-red","contexts"); - } - } catch(err) { - } - if (!storageBaseDir) { - storageBaseDir = path.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || process.env.NODE_RED_HOME,".node-red","contexts"); - } + } + if (!storageBaseDir) { + storageBaseDir = path.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || process.env.NODE_RED_HOME,".node-red", base); } } - }else{ - storageBaseDir = configs.dir; } - }, - get: function (scope, key) { - if(!storages[scope]){ - createStorage(scope); - } - try{ - storages[scope].reload(); - return storages[scope].getData("/" + key.replace(/\./g,"/")); - }catch(err){ - if(err.name === "DataError"){ - return undefined; - }else{ - throw err; - } - } - }, + }else{ + storageBaseDir = path.join(config.dir, base); + } + return storageBaseDir; +} - set: function (scope, key, value) { - if(!storages[scope]){ - createStorage(scope); - } - if(value){ - storages[scope].push("/" + key.replace(/\./g,"/"), value); +function LocalFileSystem(config){ + this.config = config; + this.storageBaseDir = getStoragePath(this.config); + this.storages = {}; +} + +LocalFileSystem.prototype.get = function (scope, key) { + if(!this.storages[scope]){ + return undefined; + } + try{ + this.storages[scope].reload(); + return this.storages[scope].getData("/" + key.replace(/\./g,"/")); + }catch(err){ + if(err.name === "DataError"){ + return undefined; }else{ - storages[scope].delete("/" + key.replace(/\./g,"/")); - } - }, - keys: function (scope) { - if(!storages[scope]){ - return []; - } - return Object.keys(storages[scope].getData("/")); - }, - delete: function(scope){ - if(storages[scope]){ - storages[scope].delete("/"); - if(scope.indexOf(":") === -1){ - fs.removeSync(path.dirname(storages[scope].filename)); - }else{ - try{ - fs.statSync(storages[scope].filename); - fs.unlinkSync(storages[scope].filename); - }catch(err){ - console.log("deleted"); - } - } - delete storages[scope]; + throw err; } } +} + +LocalFileSystem.prototype.set = function(scope, key, value) { + if(!this.storages[scope]){ + this.storages[scope] = createStorage(this.storageBaseDir ,scope); + } + if(value){ + this.storages[scope].push("/" + key.replace(/\./g,"/"), value); + }else{ + this.storages[scope].delete("/" + key.replace(/\./g,"/")); + } +} + +LocalFileSystem.prototype.keys = function(scope) { + if(!this.storages[scope]){ + return []; + } + return Object.keys(this.storages[scope].getData("/")); +} + +LocalFileSystem.prototype.delete = function(scope){ + if(this.storages[scope]){ + this.storages[scope].delete("/"); + if(scope.indexOf(":") === -1){ + fs.removeSync(path.dirname(this.storages[scope].filename)); + }else{ + try{ + fs.statSync(this.storages[scope].filename); + fs.unlinkSync(this.storages[scope].filename); + }catch(err){ + console.log("deleted"); + } + } + delete this.storages[scope]; + } +} + +module.exports = function(config){ + return new LocalFileSystem(config); }; -module.exports = localfilesystem; diff --git a/red/runtime/nodes/context/memory.js b/red/runtime/nodes/context/memory.js index 820039747..617c850eb 100644 --- a/red/runtime/nodes/context/memory.js +++ b/red/runtime/nodes/context/memory.js @@ -16,43 +16,53 @@ var util = require("../../util"); -var data; +function Memory(config){ + this.data = {}; +} -var memory = { - init: function(config) { - data = {}; - }, - get: function(scope, key) { - if(!data[scope]){ - data[scope] = {}; - } - return util.getMessageProperty(data[scope],key); - }, - set: function(scope, key, value) { - if(!data[scope]){ - data[scope] = {}; - } - util.setMessageProperty(data[scope],key,value); - }, - keys: function(scope){ - if(!data[scope]){ - data[scope] = {}; - } - var keysData = Object.keys(data[scope]); - if (scope !== "global") { - return keysData; - } else { - return keysData.filter(function (key) { - return key !== "set" && key !== "get" && key !== "keys"; - }); - } - }, - delete: function(scope){ - delete data[scope]; - }, - setGlobalContext: function(seed){ - data["global"] = seed; +Memory.prototype.get = function(scope, key) { + if(!this.data[scope]){ + return undefined; + } + return util.getMessageProperty(this.data[scope],key); +}; + +Memory.prototype.set =function(scope, key, value) { + if(!this.data[scope]){ + this.data[scope] = {}; + } + util.setMessageProperty(this.data[scope],key,value); +}; + +Memory.prototype.keys = function(scope){ + if(!this.data[scope]){ + return []; + } + if (scope !== "global") { + return Object.keys(this.data[scope]); + } else { + return Object.keys(this.data[scope]).filter(function (key) { + return key !== "set" && key !== "get" && key !== "keys"; + }); } }; -module.exports = memory; \ No newline at end of file +Memory.prototype.delete = function(scope){ + delete this.data[scope]; +}; + +Memory.prototype.open = function(){ + return true; +}; + +Memory.prototype.close = function(){ + delete this.data; +}; + +Memory.prototype.setGlobalContext= function(seed){ + this.data["global"] = seed; +}; + +module.exports = function(config){ + return new Memory(config); +}; \ No newline at end of file diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index 56135e52a..f9bf94d4d 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -151,14 +151,13 @@ describe('context', function() { var stubDelete = sinon.stub(); var contextStorage={ test:{ - module: { - init: function() { - return true; - }, - get: stubGet, - set: stubSet, - keys: stubKeys, - delete: stubDelete + module: function(config){ + function Test(){} + Test.prototype.get = stubGet; + Test.prototype.set = stubSet; + Test.prototype.keys = stubKeys; + Test.prototype.delete = stubDelete; + return new Test(config); }, config:{} } @@ -228,14 +227,13 @@ describe('context', function() { var contextDefaultStorage={ default: "test", test:{ - module: { - init: function() { - return true; - }, - get: stubGet, - set: stubSet, - keys: stubKeys, - delete: stubDelete + module: function(config){ + function Test(){} + Test.prototype.get = stubGet; + Test.prototype.set = stubSet; + Test.prototype.keys = stubKeys; + Test.prototype.delete = stubDelete; + return new Test(config); }, config:{} } diff --git a/test/red/runtime/nodes/context/localfilesystem_spec.js b/test/red/runtime/nodes/context/localfilesystem_spec.js index 8fc538c8d..f5866d338 100644 --- a/test/red/runtime/nodes/context/localfilesystem_spec.js +++ b/test/red/runtime/nodes/context/localfilesystem_spec.js @@ -17,23 +17,21 @@ var should = require('should'); var fs = require('fs-extra'); var path = require("path"); -var context = require('../../../../../red/runtime/nodes/context/localfilesystem'); +var LocalFileSystem = require('../../../../../red/runtime/nodes/context/localfilesystem'); var resourcesDir = path.resolve(path.join(__dirname,"..","resources","context")); describe('localfilesystem',function() { + var context; beforeEach(function() { - context.init({dir: resourcesDir}); + context = LocalFileSystem({dir: resourcesDir}); }); - afterEach(function() { - context.delete("nodeX"); - context.delete("nodeY"); - }); - - after(function() { - fs.removeSync(resourcesDir); + afterEach(function(done) { + fs.remove(resourcesDir).then(function(result){ + return done(result); + }); }); describe('#get/set',function() { diff --git a/test/red/runtime/nodes/context/memory_spec.js b/test/red/runtime/nodes/context/memory_spec.js index 91f4ab917..953529e38 100644 --- a/test/red/runtime/nodes/context/memory_spec.js +++ b/test/red/runtime/nodes/context/memory_spec.js @@ -15,12 +15,13 @@ **/ var should = require('should'); -var context = require('../../../../../red/runtime/nodes/context/memory'); +var Memory = require('../../../../../red/runtime/nodes/context/memory'); describe('memory',function() { + var context; beforeEach(function() { - context.init({}); + context = Memory({}); }); describe('#get/set',function() { From 7185bcd51fecbeb4017f520677cced69c102c327 Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Wed, 30 May 2018 10:24:27 +0900 Subject: [PATCH 14/32] Add open/close API for context --- red/runtime/index.js | 7 +- red/runtime/nodes/context/index.js | 35 +- red/runtime/nodes/context/localfilesystem.js | 13 +- red/runtime/nodes/context/memory.js | 16 +- red/runtime/nodes/index.js | 3 +- test/red/runtime/index_spec.js | 22 +- test/red/runtime/nodes/context/index_spec.js | 474 +++++++++--------- .../nodes/context/localfilesystem_spec.js | 8 +- 8 files changed, 318 insertions(+), 260 deletions(-) diff --git a/red/runtime/index.js b/red/runtime/index.js index 30d9c6f2b..e8af69712 100644 --- a/red/runtime/index.js +++ b/red/runtime/index.js @@ -90,6 +90,7 @@ function start() { }) .then(function() { return storage.init(runtime)}) .then(function() { return settings.load(storage)}) + .then(function() { return redNodes.loadContextsPlugin()}) .then(function() { if (log.metric()) { @@ -109,8 +110,6 @@ function start() { events.emit("runtime-event",{id:"runtime-unsupported-version",payload:{type:"error",text:"notification.errors.unsupportedVersion"},retain:true}); } log.info(os.type()+" "+os.release()+" "+os.arch()+" "+os.endianness()); - log.info("Loading external context plugins"); - redNodes.loadContextsPlugin(); return redNodes.load().then(function() { var i; @@ -231,7 +230,9 @@ function stop() { clearTimeout(reinstallTimeout); } started = false; - return redNodes.stopFlows(); + return redNodes.stopFlows().then(function(){ + return redNodes.closeContextsPlugin(); + }); } var runtime = module.exports = { diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index 30481e9d1..80fe2c774 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -17,6 +17,7 @@ var clone = require("clone"); var util = require("../../util"); var log = require("../../log"); +var when = require("when"); var settings; var contexts = {}; @@ -30,8 +31,10 @@ function init(_settings) { // init memory plugin var memory = require("./memory"); + var seed = settings.functionGlobalContext || {}; externalContexts["_"] = memory(); - globalContext = createContext("global",settings.functionGlobalContext || {}); + externalContexts["_"].setGlobalContext(seed); + globalContext = createContext("global",seed); } function load() { @@ -39,6 +42,7 @@ function load() { var plugins = settings.contextStorage; var isAlias = false; if (plugins) { + var promises = []; noContextStorage = false; for(var pluginName in plugins){ if(pluginName === "_"){ @@ -56,25 +60,32 @@ function load() { try{ plugin = require("./"+plugins[pluginName].module); }catch(err){ - throw new Error(log._("context.error-module-not-loaded", {module:plugins[pluginName].module})); + return when.reject(new Error(log._("context.error-module-not-loaded", {module:plugins[pluginName].module}))); } } else { plugin = plugins[pluginName].module; } externalContexts[pluginName] = plugin(config); }else{ - throw new Error(log._("context.error-module-not-defined", {storage:pluginName})); + return when.reject(new Error(log._("context.error-module-not-defined", {storage:pluginName}))); } } if(isAlias){ if(externalContexts.hasOwnProperty(plugins["default"])){ externalContexts["default"] = externalContexts[plugins["default"]]; }else{ - throw new Error(log._("context.error-invalid-default-module", {storage:plugins["default"]})); + return when.reject(new Error(log._("context.error-invalid-default-module", {storage:plugins["default"]}))); } } + for(var plugin in externalContexts){ + if(externalContexts.hasOwnProperty(plugin)){ + promises.push(externalContexts[plugin].open()); + } + } + return when.all(promises); } else { noContextStorage = true; + return externalContexts["_"].open(); } } @@ -157,9 +168,6 @@ function createContext(id,seed) { var context = getContextStorage(storageName); return context.keys(scope); }; - if(id === "global"){ - externalContexts["_"].setGlobalContext(seed); - } return obj; } @@ -210,10 +218,21 @@ function clean(flowConfig) { } } +function close() { + var promises = []; + for(var plugin in externalContexts){ + if(externalContexts.hasOwnProperty(plugin)){ + promises.push(externalContexts[plugin].close()); + } + } + return when.all(promises); +} + module.exports = { init: init, load: load, get: getContext, delete: deleteContext, - clean:clean + clean: clean, + close: close }; diff --git a/red/runtime/nodes/context/localfilesystem.js b/red/runtime/nodes/context/localfilesystem.js index d18f4fd76..ee693dfd5 100644 --- a/red/runtime/nodes/context/localfilesystem.js +++ b/red/runtime/nodes/context/localfilesystem.js @@ -17,11 +17,10 @@ var JsonDB = require('node-json-db'); var fs = require('fs-extra'); var path = require("path"); +var when = require("when"); function createStorage(storageBaseDir, scope) { - var i = scope.indexOf(":") - - if(i === -1){ + if(scope.indexOf(":") === -1){ if(scope === "global"){ return new JsonDB(path.join(storageBaseDir,"global",scope), true, true); }else{ // scope:flow @@ -69,6 +68,14 @@ function LocalFileSystem(config){ this.storages = {}; } +LocalFileSystem.prototype.open = function(){ + return when.resolve(); +} + +LocalFileSystem.prototype.close = function(){ + return when.resolve(); +} + LocalFileSystem.prototype.get = function (scope, key) { if(!this.storages[scope]){ return undefined; diff --git a/red/runtime/nodes/context/memory.js b/red/runtime/nodes/context/memory.js index 617c850eb..278773ca6 100644 --- a/red/runtime/nodes/context/memory.js +++ b/red/runtime/nodes/context/memory.js @@ -15,11 +15,20 @@ **/ var util = require("../../util"); +var when = require("when"); function Memory(config){ this.data = {}; } +Memory.prototype.open = function(){ + return when.resolve(); +}; + +Memory.prototype.close = function(){ + return when.resolve(); +}; + Memory.prototype.get = function(scope, key) { if(!this.data[scope]){ return undefined; @@ -49,14 +58,7 @@ Memory.prototype.keys = function(scope){ Memory.prototype.delete = function(scope){ delete this.data[scope]; -}; -Memory.prototype.open = function(){ - return true; -}; - -Memory.prototype.close = function(){ - delete this.data; }; Memory.prototype.setGlobalContext= function(seed){ diff --git a/red/runtime/nodes/index.js b/red/runtime/nodes/index.js index 139f62ffa..3321c87dc 100644 --- a/red/runtime/nodes/index.js +++ b/red/runtime/nodes/index.js @@ -219,5 +219,6 @@ module.exports = { getCredentialKeyType: credentials.getKeyType, // Contexts - loadContextsPlugin: context.load + loadContextsPlugin: context.load, + closeContextsPlugin: context.close }; diff --git a/test/red/runtime/index_spec.js b/test/red/runtime/index_spec.js index d67c12ee1..c2adfa9c2 100644 --- a/test/red/runtime/index_spec.js +++ b/test/red/runtime/index_spec.js @@ -92,7 +92,7 @@ describe("runtime", function() { redNodesCleanModuleList = sinon.stub(redNodes,"cleanModuleList",function(){}); redNodesLoadFlows = sinon.stub(redNodes,"loadFlows",function() {return when.resolve()}); redNodesStartFlows = sinon.stub(redNodes,"startFlows",function() {}); - redNodesLoadContextsPlugin = sinon.stub(redNodes,"loadContextsPlugin",function() {}); + redNodesLoadContextsPlugin = sinon.stub(redNodes,"loadContextsPlugin",function() {return when.resolve()}); }); afterEach(function() { storageInit.restore(); @@ -186,7 +186,7 @@ describe("runtime", function() { }); it("reports runtime metrics",function(done) { - var stopFlows = sinon.stub(redNodes,"stopFlows",function() {} ); + var stopFlows = sinon.stub(redNodes,"stopFlows",function() { return when.resolve();} ); redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {return []}); logMetric.restore(); logMetric = sinon.stub(log,"metric",function() { return true; }); @@ -217,10 +217,18 @@ describe("runtime", function() { }); - it("stops components", function() { - var stopFlows = sinon.stub(redNodes,"stopFlows",function() {} ); - runtime.stop(); - stopFlows.called.should.be.true(); - stopFlows.restore(); + it("stops components", function(done) { + var stopFlows = sinon.stub(redNodes,"stopFlows",function() { return when.resolve();} ); + var closeContextsPlugin = sinon.stub(redNodes,"closeContextsPlugin",function() { return when.resolve();} ); + runtime.stop().then(function(){ + stopFlows.called.should.be.true(); + closeContextsPlugin.called.should.be.true(); + done(); + }).catch(function(err){ + return done(err) + }).finally(function(){ + stopFlows.restore(); + closeContextsPlugin.restore(); + }); }); }); diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index f9bf94d4d..0a4ec3174 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -16,18 +16,18 @@ var should = require("should"); var sinon = require('sinon'); -var path = require('path'); -var fs = require('fs-extra'); var rewire = require("rewire"); var Context = require("../../../../../red/runtime/nodes/context/index"); describe('context', function() { - describe('localmemory',function() { + describe('local memory',function() { beforeEach(function() { Context.init({}); + return Context.load(); }); afterEach(function() { Context.clean({allNodes:{}}); + return Context.close(); }); it('stores local property',function() { var context1 = Context.get("1","flowA"); @@ -140,57 +140,261 @@ describe('context', function() { keys.should.have.length(2); keys[1].should.eql("abc"); }); + + it('should store data on memory when contextStorage is not defined', function() { + var context = Context.get("1","flow"); + context.set("#nonexist.key1", "val1"); + context.get("#nonexist.key1").should.eql("val1"); + context.flow.set("#nonexist.key2", "val2"); + context.flow.get("#nonexist.key2").should.eql("val2"); + context.global.set("#nonexist.key1", "val3"); + context.global.get("#nonexist.key1").should.eql("val3"); + }); }); describe('external context storage',function() { - var testDir = path.join(__dirname,".testUserHome"); - var context; - var stubGet = sinon.stub(); - var stubSet = sinon.stub(); - var stubKeys = sinon.stub(); - var stubDelete = sinon.stub(); - var contextStorage={ - test:{ - module: function(config){ - function Test(){} - Test.prototype.get = stubGet; - Test.prototype.set = stubSet; - Test.prototype.keys = stubKeys; - Test.prototype.delete = stubDelete; - return new Test(config); - }, - config:{} - } - }; + describe('load modules',function(){ + afterEach(function() { + Context.clean({allNodes:{}}); + return Context.close(); + }); - afterEach(function(done) { - stubGet.reset(); - stubSet.reset(); - stubKeys.reset(); - stubDelete.reset(); - Context.clean({allNodes:{}}); - fs.remove(testDir,done); + it('should load memory module', function() { + Context.init({contextStorage:{memory:{module:"memory"}}}); + return Context.load(); + }); + it('should load localfilesystem module', function() { + Context.init({contextStorage:{file:{module:"localfilesystem"}}}); + return Context.load(); + }); + it('should accept special storage name', function() { + Context.init({ + contextStorage:{ + "#%&":{module:"memory"}, + \u3042:{module:"memory"}, + 1:{module:"memory"}, + } + }); + return Context.load().then(function(){ + var context = Context.get("1","flow"); + context.set("##%&.sign","sign1"); + context.get("##%&.sign").should.eql("sign1"); + context.set("#\u3042.file2","file2"); + context.get("#\u3042.file2").should.eql("file2"); + context.set("#1.num","num3"); + context.get("#1.num").should.eql("num3"); + }); + }); + it('should fail when using invalid default context', function(done) { + Context.init({contextStorage:{default:"noexist"}}); + Context.load().then(function(){ + done("An error was not thrown"); + }).catch(function(){ + done(); + }); + }); + it('should fail for the storage with no module', function(done) { + Context.init({ contextStorage: { test: {}}}); + Context.load().then(function(){ + done("An error was not thrown"); + }).catch(function(){ + done(); + }); + }); + it('should fail to load non-existent module', function(done) { + Context.init({contextStorage:{ file:{module:"nonexistent"} }}); + Context.load().then(function(){ + done("An error was not thrown"); + }).catch(function(){ + done(); + }); + }); }); - function initializeContext() { - Context.init({contextStorage:contextStorage}); - Context.load(); - context = Context.get("1","flow"); - } - - describe('key name',function() { - var memoryStorage = { - memory: { - module: "memory" + describe('store data',function() { + var sandbox = sinon.sandbox.create(); + var stubGet = sandbox.stub(); + var stubSet = sandbox.stub(); + var stubKeys = sandbox.stub(); + var stubDelete = sandbox.stub(); + var stubOpen = sandbox.stub().returns(Promise.resolve()); + var stubClose = sandbox.stub().returns(Promise.resolve()); + var stubGet2 = sandbox.stub(); + var stubSet2 = sandbox.stub(); + var stubKeys2 = sandbox.stub(); + var stubDelete2 = sandbox.stub(); + var stubOpen2 = sandbox.stub().returns(Promise.resolve()); + var stubClose2 = sandbox.stub().returns(Promise.resolve()); + var testPlugin = function(config){ + function Test(){} + Test.prototype.get = stubGet; + Test.prototype.set = stubSet; + Test.prototype.keys = stubKeys; + Test.prototype.delete = stubDelete; + Test.prototype.open = stubOpen; + Test.prototype.close = stubClose; + return new Test(config); + }; + var testPlugin2 = function(config){ + function Test2(){} + Test2.prototype.get = stubGet2; + Test2.prototype.set = stubSet2; + Test2.prototype.keys = stubKeys2; + Test2.prototype.delete = stubDelete2; + Test2.prototype.open = stubOpen2; + Test2.prototype.close = stubClose2; + return new Test2(config); + }; + var contextStorage={ + test:{ + module: testPlugin, + config:{} } }; + var contextDefaultStorage={ + default: { + module: testPlugin2, + config:{} + }, + test:{ + module: testPlugin, + config:{} + } + }; + + afterEach(function() { + sandbox.reset(); + Context.clean({allNodes:{}}); + return Context.close(); + }); + + it('should store local property to external context storage',function() { + Context.init({contextStorage:contextStorage}); + return Context.load().then(function(){ + var context = Context.get("1","flow"); + should.not.exist(context.get("#test.foo")); + context.set("#test.foo","test"); + context.get("#test.foo"); + context.keys("#test"); + stubSet.calledWithExactly("1:flow","foo","test").should.be.true(); + stubGet.calledWithExactly("1:flow","foo").should.be.true(); + stubKeys.calledWithExactly("1:flow").should.be.true(); + }); + }); + it('should store flow property to external context storage',function() { + Context.init({contextStorage:contextStorage}); + return Context.load().then(function(){ + var context = Context.get("1","flow"); + should.not.exist(context.flow.get("#test.foo")); + context.flow.set("#test.foo","test"); + context.flow.get("#test.foo"); + context.flow.keys("#test"); + stubSet.calledWithExactly("flow","foo","test").should.be.true(); + stubGet.calledWithExactly("flow","foo").should.be.true(); + stubKeys.calledWithExactly("flow").should.be.true(); + }); + }); + it('should store global property to external context storage',function() { + Context.init({contextStorage:contextStorage}); + return Context.load().then(function(){ + var context = Context.get("1","flow"); + should.not.exist(context.global.get("#test.foo")); + context.global.set("#test.foo","test"); + context.global.get("#test.foo"); + context.global.keys("#test"); + stubSet.calledWithExactly("global","foo","test").should.be.true(); + stubGet.calledWithExactly("global","foo").should.be.true(); + stubKeys.calledWithExactly("global").should.be.true(); + }); + }); + it('should store data to the default context when non-existent context storage was specified', function() { + Context.init({contextStorage:contextDefaultStorage}); + return Context.load().then(function(){ + var context = Context.get("1","flow"); + should.not.exist(context.get("#nonexist.foo")); + context.set("#nonexist.foo","test"); + context.get("#nonexist.foo"); + context.keys("#nonexist"); + stubGet.called.should.be.false(); + stubSet.called.should.be.false(); + stubKeys.called.should.be.false(); + stubSet2.calledWithExactly("1:flow","foo","test").should.be.true(); + stubGet2.calledWithExactly("1:flow","foo").should.be.true(); + stubKeys2.calledWithExactly("1:flow").should.be.true(); + }); + }); + it('should use the default context', function() { + Context.init({contextStorage:contextDefaultStorage}); + return Context.load().then(function(){ + var context = Context.get("1","flow"); + should.not.exist(context.get("#default.foo")); + context.set("#default.foo","default"); + context.get("#default.foo"); + context.keys("#default"); + stubGet.called.should.be.false(); + stubSet.called.should.be.false(); + stubKeys.called.should.be.false(); + stubSet2.calledWithExactly("1:flow","foo","default").should.be.true(); + stubGet2.calledWithExactly("1:flow","foo").should.be.true(); + stubKeys2.calledWithExactly("1:flow").should.be.true(); + }); + }); + it('should use the alias of default context', function() { + Context.init({contextStorage:contextDefaultStorage}); + return Context.load().then(function(){ + var context = Context.get("1","flow"); + should.not.exist(context.get("#.foo")); + context.set("#.foo","alias"); + context.get("#.foo"); + context.keys("#"); + stubGet.called.should.be.false(); + stubSet.called.should.be.false(); + stubKeys.called.should.be.false(); + stubSet2.calledWithExactly("1:flow","foo","alias").should.be.true(); + stubGet2.calledWithExactly("1:flow","foo").should.be.true(); + stubKeys2.calledWithExactly("1:flow").should.be.true(); + }); + }); + it('should throw an error using undefined storage for local context', function(done) { + Context.init({contextStorage:contextStorage}); + Context.load().then(function(){ + var context = Context.get("1","flow"); + context.get("#nonexist.local"); + should.fail(null, null, "An error was not thrown using undefined storage for local context"); + }).catch(function(err) { + if (err.name === "ContextError") { + done(); + } else { + done(err); + } + }); + }); + it('should throw an error using undefined storage for flow context', function(done) { + Context.init({contextStorage:contextStorage}); + Context.load().then(function(){ + var context = Context.get("1","flow"); + context.flow.set("#nonexist.flow"); + should.fail(null, null, "An error was not thrown using undefined storage for flow context"); + }).catch(function(err) { + if (err.name === "ContextError") { + done(); + } else { + done(err); + } + }); + }); + }); + + describe('key name',function() { beforeEach(function() { - Context.init({contextStorage:memoryStorage}); - Context.load(); - context = Context.get("1","flow"); + Context.init({contextStorage:{memory:{module:"memory"}}}); + return Context.load().then(function(){ + context = Context.get("1","flow"); + }); }); afterEach(function() { Context.clean({allNodes:{}}); + return Context.close(); }); it('should work correctly with the valid key name',function() { context.set("#memory.azAZ09#_","valid"); @@ -222,192 +426,6 @@ describe('context', function() { }).should.throw(); }); }); - - describe('if external context storage exists',function() { - var contextDefaultStorage={ - default: "test", - test:{ - module: function(config){ - function Test(){} - Test.prototype.get = stubGet; - Test.prototype.set = stubSet; - Test.prototype.keys = stubKeys; - Test.prototype.delete = stubDelete; - return new Test(config); - }, - config:{} - } - }; - - it('should store local property to external context storage',function() { - initializeContext(); - should.not.exist(context.get("#test.foo")); - context.set("#test.foo","test"); - context.get("#test.foo"); - context.keys("#test"); - stubGet.called.should.be.true(); - stubSet.called.should.be.true(); - stubKeys.called.should.be.true(); - }); - it('should store flow property to external context storage',function() { - initializeContext(); - should.not.exist(context.flow.get("#test.foo")); - context.flow.set("#test.foo","test"); - context.flow.get("#test.foo"); - context.flow.keys("#test"); - stubGet.called.should.be.true(); - stubSet.called.should.be.true(); - stubKeys.called.should.be.true(); - }); - it('should store global property to external context storage',function() { - initializeContext(); - should.not.exist(context.global.get("#test.foo")); - context.global.set("#test.foo","test"); - context.global.get("#test.foo"); - context.global.keys("#test"); - stubGet.called.should.be.true(); - stubSet.called.should.be.true(); - stubKeys.called.should.be.true(); - }); - it('should store data when non-existent context storage was specified', function() { - Context.init({contextStorage:contextDefaultStorage}); - Context.load(); - context = Context.get("1","flow"); - should.not.exist(context.get("#nonexist.foo")); - context.set("#nonexist.foo","test"); - context.get("#nonexist.foo"); - context.keys("#nonexist"); - stubGet.called.should.be.true(); - stubSet.called.should.be.true(); - stubKeys.called.should.be.true(); - }); - it('should use the default context', function() { - Context.init({contextStorage:contextDefaultStorage}); - Context.load(); - context = Context.get("1","flow"); - should.not.exist(context.get("#default.foo")); - context.set("#default.foo","default"); - context.get("#default.foo"); - context.keys("#default"); - stubGet.called.should.be.true(); - stubSet.called.should.be.true(); - stubKeys.called.should.be.true(); - }); - it('should use the alias of default context', function() { - Context.init({contextStorage:contextDefaultStorage}); - Context.load(); - context = Context.get("1","flow"); - should.not.exist(context.get("#.foo")); - context.set("#.foo","alias"); - context.get("#.foo"); - context.keys("#"); - stubGet.called.should.be.true(); - stubSet.called.should.be.true(); - stubKeys.called.should.be.true(); - }); - it('should load memory module', function(done) { - Context.init({ contextStorage: { _: {}}}); - try { - Context.load(); - context.set("#_.foo","mem1"); - context.get("#_.foo").should.eql("mem1"); - var keys = context.keys("#_"); - keys.should.have.length(1); - keys[0].should.eql("foo"); - done(); - } catch (err) { - done(err); - } - }); - it('should load localfilesystem module', function(done) { - Context.init({contextStorage:{ file:{module:"localfilesystem"} }}); - try { - Context.load(); - done(); - } catch (err) { - done(err); - } - }); - it('should accept special storage name', function(done) { - Context.init({ - contextStorage:{ - "#%&":{module:"memory"}, - \u3042:{module:"memory"}, - 1:{module:"memory"}, - } - }); - try { - Context.load(); - context.set("##%&.sign","sign1"); - context.get("##%&.sign").should.eql("sign1"); - context.set("#\u3042.file2","file2"); - context.get("#\u3042.file2").should.eql("file2"); - context.set("#1.num","num3"); - context.get("#1.num").should.eql("num3"); - done(); - } catch (err) { - done(err); - } - }); - }); - - describe('if external context storage does not exist',function() { - it('should throw an error using undefined storage for local context', function(done) { - initializeContext(); - try { - context.get("#nonexist.local"); - should.fail(null, null, "An error was not thrown using undefined storage for local context"); - } catch (err) { - if (err.name === "ContextError") { - done(); - } else { - done(err); - } - } - }); - it('should throw an error using undefined storage for flow context', function(done) { - initializeContext(); - try { - context.flow.set("#nonexist.flow"); - should.fail(null, null, "An error was not thrown using undefined storage for flow context"); - } catch (err) { - if (err.name === "ContextError") { - done(); - } else { - done(err); - } - } - }); - it('should fail when using invalid default context', function() { - Context.init({contextStorage:{default:"noexist"}}); - (function() { - Context.load(); - }).should.throw(); - }); - it('should store data on memory when contextStorage is not defined', function() { - Context.init({}); - Context.load(); - context = Context.get("1","flow"); - context.set("#nonexist.key1", "val1"); - context.get("#nonexist.key1").should.eql("val1"); - context.flow.set("#nonexist.key2", "val2"); - context.flow.get("#nonexist.key2").should.eql("val2"); - context.global.set("#nonexist.key1", "val3"); - context.global.get("#nonexist.key1").should.eql("val3"); - }); - it('should fail for the storage with no module', function() { - Context.init({ contextStorage: { test: {}}}); - (function() { - Context.load(); - }).should.throw(); - }); - it('should fail to load non-existent module', function() { - Context.init({contextStorage:{ file:{module:"nonexistent"} }}); - (function() { - Context.load(); - }).should.throw(); - }); - }); }); describe('#parseKey()', function() { diff --git a/test/red/runtime/nodes/context/localfilesystem_spec.js b/test/red/runtime/nodes/context/localfilesystem_spec.js index f5866d338..fae96bcd4 100644 --- a/test/red/runtime/nodes/context/localfilesystem_spec.js +++ b/test/red/runtime/nodes/context/localfilesystem_spec.js @@ -17,6 +17,7 @@ var should = require('should'); var fs = require('fs-extra'); var path = require("path"); +var when = require("when"); var LocalFileSystem = require('../../../../../red/runtime/nodes/context/localfilesystem'); var resourcesDir = path.resolve(path.join(__dirname,"..","resources","context")); @@ -26,11 +27,12 @@ describe('localfilesystem',function() { beforeEach(function() { context = LocalFileSystem({dir: resourcesDir}); + return context.open(); }); - afterEach(function(done) { - fs.remove(resourcesDir).then(function(result){ - return done(result); + afterEach(function() { + return context.close().then(function(){ + return fs.remove(resourcesDir); }); }); From f2623484970aa612a933fa8c8f0df8e8d2a8ee37 Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Wed, 30 May 2018 15:23:34 +0900 Subject: [PATCH 15/32] Add clean to context plugin and don't delete local context unless the context is deleted by a user --- red/runtime/nodes/Node.js | 4 +- red/runtime/nodes/context/index.js | 40 ++-- red/runtime/nodes/context/localfilesystem.js | 36 ++-- red/runtime/nodes/context/memory.js | 14 +- red/runtime/nodes/flows/index.js | 9 +- test/red/runtime/nodes/context/index_spec.js | 183 ++++++++++++------ .../nodes/context/localfilesystem_spec.js | 40 +++- test/red/runtime/nodes/context/memory_spec.js | 41 +++- 8 files changed, 260 insertions(+), 107 deletions(-) diff --git a/red/runtime/nodes/Node.js b/red/runtime/nodes/Node.js index b8cfa6532..308d30125 100644 --- a/red/runtime/nodes/Node.js +++ b/red/runtime/nodes/Node.js @@ -105,12 +105,12 @@ Node.prototype.close = function(removed) { if (promises.length > 0) { return when.settle(promises).then(function() { if (this._context) { - context.delete(this._alias||this.id,this.z); + return context.delete(this._alias||this.id,this.z); } }); } else { if (this._context) { - context.delete(this._alias||this.id,this.z); + return context.delete(this._alias||this.id,this.z); } return; } diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index 80fe2c774..4a3f6c3d5 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -70,6 +70,11 @@ function load() { return when.reject(new Error(log._("context.error-module-not-defined", {storage:pluginName}))); } } + for(var plugin in externalContexts){ + if(externalContexts.hasOwnProperty(plugin)){ + promises.push(externalContexts[plugin].open()); + } + } if(isAlias){ if(externalContexts.hasOwnProperty(plugins["default"])){ externalContexts["default"] = externalContexts[plugins["default"]]; @@ -77,11 +82,6 @@ function load() { return when.reject(new Error(log._("context.error-invalid-default-module", {storage:plugins["default"]}))); } } - for(var plugin in externalContexts){ - if(externalContexts.hasOwnProperty(plugin)){ - promises.push(externalContexts[plugin].open()); - } - } return when.all(promises); } else { noContextStorage = true; @@ -191,31 +191,35 @@ function getContext(localId,flowId) { } function deleteContext(id,flowId) { - var contextId = id; - if (flowId) { - contextId = id+":"+flowId; + if(noContextStorage){ + var promises = []; + var contextId = id; + if (flowId) { + contextId = id+":"+flowId; + } + delete contexts[contextId]; + return externalContexts["_"].delete(contextId); + }else{ + return when.resolve(); } - for(var plugin in externalContexts){ - externalContexts[plugin].delete(contextId); - } - delete contexts[contextId]; } function clean(flowConfig) { - var activeIds = {}; - var contextId; - var node; + var promises = []; + for(var plugin in externalContexts){ + if(externalContexts.hasOwnProperty(plugin)){ + promises.push(externalContexts[plugin].clean(Object.keys(flowConfig.allNodes))); + } + } for (var id in contexts) { if (contexts.hasOwnProperty(id)) { var idParts = id.split(":"); if (!flowConfig.allNodes.hasOwnProperty(idParts[0])) { - for(var plugin in externalContexts){ - externalContexts[plugin].delete(id); - } delete contexts[id]; } } } + return when.all(promises); } function close() { diff --git a/red/runtime/nodes/context/localfilesystem.js b/red/runtime/nodes/context/localfilesystem.js index ee693dfd5..2cffe7d96 100644 --- a/red/runtime/nodes/context/localfilesystem.js +++ b/red/runtime/nodes/context/localfilesystem.js @@ -78,7 +78,7 @@ LocalFileSystem.prototype.close = function(){ LocalFileSystem.prototype.get = function (scope, key) { if(!this.storages[scope]){ - return undefined; + this.storages[scope] = createStorage(this.storageBaseDir ,scope); } try{ this.storages[scope].reload(); @@ -105,28 +105,38 @@ LocalFileSystem.prototype.set = function(scope, key, value) { LocalFileSystem.prototype.keys = function(scope) { if(!this.storages[scope]){ - return []; + this.storages[scope] = createStorage(this.storageBaseDir ,scope); } return Object.keys(this.storages[scope].getData("/")); } LocalFileSystem.prototype.delete = function(scope){ + var self = this; if(this.storages[scope]){ + var promise; this.storages[scope].delete("/"); - if(scope.indexOf(":") === -1){ - fs.removeSync(path.dirname(this.storages[scope].filename)); - }else{ - try{ - fs.statSync(this.storages[scope].filename); - fs.unlinkSync(this.storages[scope].filename); - }catch(err){ - console.log("deleted"); - } - } - delete this.storages[scope]; + return fs.remove(this.storages[scope].filename).then(function(){ + delete self.storages[scope]; + return when.resolve(); + }); + }else{ + return when.resolve(); } } +LocalFileSystem.prototype.clean = function(activeNodes){ + var self = this; + return fs.readdir(self.storageBaseDir).then(function(dirs){ + return when.all(dirs.reduce(function(result, item){ + if(item !== "global" && !activeNodes.includes(item)){ + result.push(fs.remove(path.join(self.storageBaseDir,item))); + delete self.storages[item]; + } + return result; + },[])); + }); + } + module.exports = function(config){ return new LocalFileSystem(config); }; diff --git a/red/runtime/nodes/context/memory.js b/red/runtime/nodes/context/memory.js index 278773ca6..ba86d086d 100644 --- a/red/runtime/nodes/context/memory.js +++ b/red/runtime/nodes/context/memory.js @@ -58,9 +58,21 @@ Memory.prototype.keys = function(scope){ Memory.prototype.delete = function(scope){ delete this.data[scope]; - + return Promise.resolve(); }; +Memory.prototype.clean = function(activeNodes){ + for(var id in this.data){ + if(this.data.hasOwnProperty(id) && id !== "global"){ + var idParts = id.split(":"); + if(!activeNodes.includes(idParts[0])){ + delete this.data[id]; + } + } + } + return Promise.resolve(); +} + Memory.prototype.setGlobalContext= function(seed){ this.data["global"] = seed; }; diff --git a/red/runtime/nodes/flows/index.js b/red/runtime/nodes/flows/index.js index 1b2a87ac0..0f12e4746 100644 --- a/red/runtime/nodes/flows/index.js +++ b/red/runtime/nodes/flows/index.js @@ -160,11 +160,12 @@ function setFlows(_config,type,muteLog,forceStart) { activeFlowConfig = newFlowConfig; if (forceStart || started) { return stop(type,diff,muteLog).then(function() { - context.clean(activeFlowConfig); - start(type,diff,muteLog).then(function() { - events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true}); + return context.clean(activeFlowConfig).then(function() { + start(type,diff,muteLog).then(function() { + events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true}); + }); + return flowRevision; }); - return flowRevision; }).catch(function(err) { }) } else { diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index 0a4ec3174..fa048aa31 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -118,9 +118,10 @@ describe('context', function() { context.set("foo","abc"); context.get("foo").should.eql("abc"); - Context.delete("1","flowA"); - context = Context.get("1","flowA"); - should.not.exist(context.get("foo")); + return Context.delete("1","flowA").then(function(){ + context = Context.get("1","flowA"); + should.not.exist(context.get("foo")); + }); }); it('enumerates context keys', function() { @@ -153,12 +154,75 @@ describe('context', function() { }); describe('external context storage',function() { - describe('load modules',function(){ - afterEach(function() { - Context.clean({allNodes:{}}); + var sandbox = sinon.sandbox.create(); + var stubGet = sandbox.stub(); + var stubSet = sandbox.stub(); + var stubKeys = sandbox.stub(); + var stubDelete = sandbox.stub().returns(Promise.resolve()); + var stubClean = sandbox.stub().returns(Promise.resolve()); + var stubOpen = sandbox.stub().returns(Promise.resolve()); + var stubClose = sandbox.stub().returns(Promise.resolve()); + var stubGet2 = sandbox.stub(); + var stubSet2 = sandbox.stub(); + var stubKeys2 = sandbox.stub(); + var stubDelete2 = sandbox.stub().returns(Promise.resolve()); + var stubClean2 = sandbox.stub().returns(Promise.resolve()); + var stubOpen2 = sandbox.stub().returns(Promise.resolve()); + var stubClose2 = sandbox.stub().returns(Promise.resolve()); + var testPlugin = function(config){ + function Test(){} + Test.prototype.get = stubGet; + Test.prototype.set = stubSet; + Test.prototype.keys = stubKeys; + Test.prototype.delete = stubDelete; + Test.prototype.clean = stubClean; + Test.prototype.open = stubOpen; + Test.prototype.close = stubClose; + return new Test(config); + }; + var testPlugin2 = function(config){ + function Test2(){} + Test2.prototype.get = stubGet2; + Test2.prototype.set = stubSet2; + Test2.prototype.keys = stubKeys2; + Test2.prototype.delete = stubDelete2; + Test2.prototype.clean = stubClean2; + Test2.prototype.open = stubOpen2; + Test2.prototype.close = stubClose2; + return new Test2(config); + }; + var contextStorage={ + test:{ + module: testPlugin, + config:{} + } + }; + var contextDefaultStorage={ + default: { + module: testPlugin2, + config:{} + }, + test:{ + module: testPlugin, + config:{} + } + }; + + afterEach(function() { + sandbox.reset(); + return Context.clean({allNodes:{}}).then(function(){ return Context.close(); }); - + }); + + describe('load modules',function(){ + it('should call open()', function() { + Context.init({contextStorage:contextDefaultStorage}); + return Context.load().then(function(){ + stubOpen.called.should.be.true(); + stubOpen2.called.should.be.true(); + }); + }); it('should load memory module', function() { Context.init({contextStorage:{memory:{module:"memory"}}}); return Context.load(); @@ -185,6 +249,18 @@ describe('context', function() { context.get("#1.num").should.eql("num3"); }); }); + it('should ignore reserved storage name `_`', function() { + Context.init({contextStorage:{_:{module:testPlugin}}}); + return Context.load().then(function(){ + var context = Context.get("1","flow"); + context.set("#_.foo","bar"); + context.get("#_.foo"); + context.keys("#_"); + stubSet.called.should.be.false(); + stubGet.called.should.be.false(); + stubKeys.called.should.be.false(); + }); + }); it('should fail when using invalid default context', function(done) { Context.init({contextStorage:{default:"noexist"}}); Context.load().then(function(){ @@ -211,63 +287,19 @@ describe('context', function() { }); }); - describe('store data',function() { - var sandbox = sinon.sandbox.create(); - var stubGet = sandbox.stub(); - var stubSet = sandbox.stub(); - var stubKeys = sandbox.stub(); - var stubDelete = sandbox.stub(); - var stubOpen = sandbox.stub().returns(Promise.resolve()); - var stubClose = sandbox.stub().returns(Promise.resolve()); - var stubGet2 = sandbox.stub(); - var stubSet2 = sandbox.stub(); - var stubKeys2 = sandbox.stub(); - var stubDelete2 = sandbox.stub(); - var stubOpen2 = sandbox.stub().returns(Promise.resolve()); - var stubClose2 = sandbox.stub().returns(Promise.resolve()); - var testPlugin = function(config){ - function Test(){} - Test.prototype.get = stubGet; - Test.prototype.set = stubSet; - Test.prototype.keys = stubKeys; - Test.prototype.delete = stubDelete; - Test.prototype.open = stubOpen; - Test.prototype.close = stubClose; - return new Test(config); - }; - var testPlugin2 = function(config){ - function Test2(){} - Test2.prototype.get = stubGet2; - Test2.prototype.set = stubSet2; - Test2.prototype.keys = stubKeys2; - Test2.prototype.delete = stubDelete2; - Test2.prototype.open = stubOpen2; - Test2.prototype.close = stubClose2; - return new Test2(config); - }; - var contextStorage={ - test:{ - module: testPlugin, - config:{} - } - }; - var contextDefaultStorage={ - default: { - module: testPlugin2, - config:{} - }, - test:{ - module: testPlugin, - config:{} - } - }; - - afterEach(function() { - sandbox.reset(); - Context.clean({allNodes:{}}); - return Context.close(); + describe('close modules',function(){ + it('should call close()', function() { + Context.init({contextStorage:contextDefaultStorage}); + return Context.load().then(function(){ + return Context.close().then(function(){ + stubClose.called.should.be.true(); + stubClose2.called.should.be.true(); + }); + }); }); - + }); + + describe('store context',function() { it('should store local property to external context storage',function() { Context.init({contextStorage:contextStorage}); return Context.load().then(function(){ @@ -385,6 +417,31 @@ describe('context', function() { }); }); + describe('delete context',function(){ + it('should not call delete()', function() { + Context.init({contextStorage:contextDefaultStorage}); + return Context.load().then(function(){ + Context.get("flowA"); + return Context.delete("flowA").then(function(){ + stubDelete.called.should.be.false(); + stubDelete2.called.should.be.false(); + }); + }); + }); + }); + + describe('clean context',function(){ + it('should call clean()', function() { + Context.init({contextStorage:contextDefaultStorage}); + return Context.load().then(function(){ + return Context.clean({allNodes:{}}).then(function(){ + stubClean.calledWithExactly([]).should.be.true(); + stubClean2.calledWithExactly([]).should.be.true(); + }); + }); + }); + }); + describe('key name',function() { beforeEach(function() { Context.init({contextStorage:{memory:{module:"memory"}}}); diff --git a/test/red/runtime/nodes/context/localfilesystem_spec.js b/test/red/runtime/nodes/context/localfilesystem_spec.js index fae96bcd4..77a6c440d 100644 --- a/test/red/runtime/nodes/context/localfilesystem_spec.js +++ b/test/red/runtime/nodes/context/localfilesystem_spec.js @@ -31,8 +31,10 @@ describe('localfilesystem',function() { }); afterEach(function() { - return context.close().then(function(){ - return fs.remove(resourcesDir); + return context.clean([]).then(function(){ + return context.close().then(function(){ + return fs.remove(resourcesDir); + }); }); }); @@ -118,9 +120,39 @@ describe('localfilesystem',function() { context.get("nodeX","foo").should.eql("abc"); context.get("nodeY","foo").should.eql("abc"); - context.delete("nodeX"); + return context.delete("nodeX").then(function(){ + should.not.exist(context.get("nodeX","foo")); + should.exist(context.get("nodeY","foo")); + }) + }); + }); + + describe('#clean',function() { + it('should clean unnecessary context',function() { should.not.exist(context.get("nodeX","foo")); - should.exist(context.get("nodeY","foo")); + should.not.exist(context.get("nodeY","foo")); + context.set("nodeX","foo","abc"); + context.set("nodeY","foo","abc"); + context.get("nodeX","foo").should.eql("abc"); + context.get("nodeY","foo").should.eql("abc"); + + return context.clean([]).then(function(){ + should.not.exist(context.get("nodeX","foo")); + should.not.exist(context.get("nodeY","foo")); + }); + }); + it('should not clean active context',function() { + should.not.exist(context.get("nodeX","foo")); + should.not.exist(context.get("nodeY","foo")); + context.set("nodeX","foo","abc"); + context.set("nodeY","foo","abc"); + context.get("nodeX","foo").should.eql("abc"); + context.get("nodeY","foo").should.eql("abc"); + + return context.clean(["nodeX"]).then(function(){ + should.exist(context.get("nodeX","foo")); + should.not.exist(context.get("nodeY","foo")); + }); }); }); }); \ No newline at end of file diff --git a/test/red/runtime/nodes/context/memory_spec.js b/test/red/runtime/nodes/context/memory_spec.js index 953529e38..8da35be19 100644 --- a/test/red/runtime/nodes/context/memory_spec.js +++ b/test/red/runtime/nodes/context/memory_spec.js @@ -22,6 +22,13 @@ describe('memory',function() { beforeEach(function() { context = Memory({}); + return context.open(); + }); + + afterEach(function() { + return context.clean([]).then(function(){ + return context.close(); + }); }); describe('#get/set',function() { @@ -120,9 +127,39 @@ describe('memory',function() { context.get("nodeX","foo").should.eql("abc"); context.get("nodeY","foo").should.eql("abc"); - context.delete("nodeX"); + return context.delete("nodeX").then(function(){ + should.not.exist(context.get("nodeX","foo")); + should.exist(context.get("nodeY","foo")); + }); + }); + }); + + describe('#clean',function() { + it('should clean unnecessary context',function() { should.not.exist(context.get("nodeX","foo")); - should.exist(context.get("nodeY","foo")); + should.not.exist(context.get("nodeY","foo")); + context.set("nodeX","foo","abc"); + context.set("nodeY","foo","abc"); + context.get("nodeX","foo").should.eql("abc"); + context.get("nodeY","foo").should.eql("abc"); + + return context.clean([]).then(function(){ + should.not.exist(context.get("nodeX","foo")); + should.not.exist(context.get("nodeY","foo")); + }); + }); + it('should not clean active context',function() { + should.not.exist(context.get("nodeX","foo")); + should.not.exist(context.get("nodeY","foo")); + context.set("nodeX","foo","abc"); + context.set("nodeY","foo","abc"); + context.get("nodeX","foo").should.eql("abc"); + context.get("nodeY","foo").should.eql("abc"); + + return context.clean(["nodeX"]).then(function(){ + should.exist(context.get("nodeX","foo")); + should.not.exist(context.get("nodeY","foo")); + }); }); }); From c48c74f173af65163d4a6397abca4e0e188b3912 Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Thu, 31 May 2018 09:46:54 +0900 Subject: [PATCH 16/32] Delete unused variables --- red/runtime/nodes/context/index.js | 3 --- red/runtime/nodes/context/localfilesystem.js | 1 - 2 files changed, 4 deletions(-) diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index 4a3f6c3d5..d1bf32be3 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -15,7 +15,6 @@ **/ var clone = require("clone"); -var util = require("../../util"); var log = require("../../log"); var when = require("when"); @@ -163,7 +162,6 @@ function createContext(id,seed) { return context.set(scope, keyPath.key, value); }; obj.keys = function(storage) { - //TODO: discuss about keys() behavior var storageName = parseStorage(storage); var context = getContextStorage(storageName); return context.keys(scope); @@ -192,7 +190,6 @@ function getContext(localId,flowId) { function deleteContext(id,flowId) { if(noContextStorage){ - var promises = []; var contextId = id; if (flowId) { contextId = id+":"+flowId; diff --git a/red/runtime/nodes/context/localfilesystem.js b/red/runtime/nodes/context/localfilesystem.js index 2cffe7d96..60098cc91 100644 --- a/red/runtime/nodes/context/localfilesystem.js +++ b/red/runtime/nodes/context/localfilesystem.js @@ -113,7 +113,6 @@ LocalFileSystem.prototype.keys = function(scope) { LocalFileSystem.prototype.delete = function(scope){ var self = this; if(this.storages[scope]){ - var promise; this.storages[scope].delete("/"); return fs.remove(this.storages[scope].filename).then(function(){ delete self.storages[scope]; From 16715673c3a3b95c8f61f547e4352898ce77063b Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Thu, 31 May 2018 09:47:21 +0900 Subject: [PATCH 17/32] Add test case --- test/red/runtime/nodes/context/index_spec.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index fa048aa31..c4bfa12ed 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -142,6 +142,16 @@ describe('context', function() { keys[1].should.eql("abc"); }); + it('should enumerate only context keys when GlobalContext was given', function() { + Context.init({functionGlobalContext: {foo:"bar"}}); + return Context.load().then(function(){ + var context = Context.get("1","flowA"); + var keys = context.global.keys("global"); + keys.should.have.length(1); + keys[0].should.eql("foo"); + }); + }); + it('should store data on memory when contextStorage is not defined', function() { var context = Context.get("1","flow"); context.set("#nonexist.key1", "val1"); From a835f9f0cb0c009f5fead2840155b82b8ec5e079 Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Thu, 31 May 2018 14:09:36 +0900 Subject: [PATCH 18/32] Fix ENOENT error in LocalFileSystem.clean() --- red/runtime/nodes/context/localfilesystem.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/red/runtime/nodes/context/localfilesystem.js b/red/runtime/nodes/context/localfilesystem.js index 60098cc91..4909f9c14 100644 --- a/red/runtime/nodes/context/localfilesystem.js +++ b/red/runtime/nodes/context/localfilesystem.js @@ -133,6 +133,12 @@ LocalFileSystem.prototype.clean = function(activeNodes){ } return result; },[])); + }).catch(function(err){ + if(err.code == 'ENOENT') { + return when.resolve(); + }else{ + return when.reject(err); + } }); } From 6e34f0697c8e6787eda0b4e2e45101924d01d6d5 Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Fri, 1 Jun 2018 11:44:45 +0900 Subject: [PATCH 19/32] Allow .get/set/keys to return asynchronous results --- red/runtime/nodes/context/index.js | 35 +++++++++++++++++--- red/runtime/nodes/context/localfilesystem.js | 12 +++++++ red/runtime/nodes/context/memory.js | 14 +++++++- 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index d1bf32be3..57fc3a747 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -118,7 +118,7 @@ function parseKey(key) { } var keyPath = { storage: "", key: "" }; var indexDot = key.indexOf("."); - // The key of "$file" should be treated as a key without persistable context. + // The key of "#file" should be treated as a key without persistable context. if (indexDot != -1) { keyPath.storage = parseStorage(key); } @@ -154,17 +154,44 @@ function createContext(id,seed) { obj.get = function(key) { var keyPath = parseKey(key); var context = getContextStorage(keyPath.storage); - return context.get(scope, keyPath.key); + if(key === keyPath.key){ + return context.get(scope, keyPath.key); + }else{ + return context.getAsync(scope, keyPath.key); + } }; obj.set = function(key, value) { var keyPath = parseKey(key); var context = getContextStorage(keyPath.storage); - return context.set(scope, keyPath.key, value); + if(key === keyPath.key){ + return context.set(scope, keyPath.key, value); + }else{ + return context.setAsync(scope, keyPath.key, value); + } }; obj.keys = function(storage) { var storageName = parseStorage(storage); var context = getContextStorage(storageName); - return context.keys(scope); + if(!storage){ + return context.keys(scope); + }else{ + return context.keysAsync(scope); + } + }; + obj.getAsync = function(key) { + var keyPath = parseKey(key); + var context = getContextStorage(keyPath.storage); + return context.getAsync(scope, keyPath.key); + }; + obj.setAsync = function(key, value) { + var keyPath = parseKey(key); + var context = getContextStorage(keyPath.storage); + return context.setAsync(scope, keyPath.key, value); + }; + obj.keysAsync = function(storage) { + var storageName = parseStorage(storage); + var context = getContextStorage(storageName); + return context.keysAsync(scope); }; return obj; } diff --git a/red/runtime/nodes/context/localfilesystem.js b/red/runtime/nodes/context/localfilesystem.js index 4909f9c14..730f0020c 100644 --- a/red/runtime/nodes/context/localfilesystem.js +++ b/red/runtime/nodes/context/localfilesystem.js @@ -110,6 +110,18 @@ LocalFileSystem.prototype.keys = function(scope) { return Object.keys(this.storages[scope].getData("/")); } +LocalFileSystem.prototype.getAsync = function(scope, key) { + return when.resolve(this.get(scope, key)); +}; + +LocalFileSystem.prototype.setAsync =function(scope, key, value) { + return when.resolve(this.set(scope, key, value)); +}; + +LocalFileSystem.prototype.keysAsync = function(scope){ + return when.resolve(this.keys(scope)); +}; + LocalFileSystem.prototype.delete = function(scope){ var self = this; if(this.storages[scope]){ diff --git a/red/runtime/nodes/context/memory.js b/red/runtime/nodes/context/memory.js index ba86d086d..1d9711094 100644 --- a/red/runtime/nodes/context/memory.js +++ b/red/runtime/nodes/context/memory.js @@ -51,11 +51,23 @@ Memory.prototype.keys = function(scope){ return Object.keys(this.data[scope]); } else { return Object.keys(this.data[scope]).filter(function (key) { - return key !== "set" && key !== "get" && key !== "keys"; + return key !== "set" && key !== "get" && key !== "keys" && key !== "setAsync" && key !== "getAsync" && key !== "keysAsync"; }); } }; +Memory.prototype.getAsync = function(scope, key) { + return when.resolve(this.get(scope, key)); +}; + +Memory.prototype.setAsync =function(scope, key, value) { + return when.resolve(this.set(scope, key, value)); +}; + +Memory.prototype.keysAsync = function(scope){ + return when.resolve(this.keys(scope)); +}; + Memory.prototype.delete = function(scope){ delete this.data[scope]; return Promise.resolve(); From fbe0e2d6ebfc5e208c7a493a8c1e76eef469feda Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Fri, 1 Jun 2018 19:21:41 +0900 Subject: [PATCH 20/32] Delete async function in context/index --- red/runtime/nodes/context/index.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index 57fc3a747..02f5a933d 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -178,21 +178,6 @@ function createContext(id,seed) { return context.keysAsync(scope); } }; - obj.getAsync = function(key) { - var keyPath = parseKey(key); - var context = getContextStorage(keyPath.storage); - return context.getAsync(scope, keyPath.key); - }; - obj.setAsync = function(key, value) { - var keyPath = parseKey(key); - var context = getContextStorage(keyPath.storage); - return context.setAsync(scope, keyPath.key, value); - }; - obj.keysAsync = function(storage) { - var storageName = parseStorage(storage); - var context = getContextStorage(storageName); - return context.keysAsync(scope); - }; return obj; } From 7aced85a310f5ba31dc92f5eca62cb9e211ea198 Mon Sep 17 00:00:00 2001 From: Hiroki Uchikawa Date: Mon, 4 Jun 2018 13:04:56 +0900 Subject: [PATCH 21/32] Use Array.indexOf() instead of Array.includes() --- red/runtime/nodes/context/localfilesystem.js | 2 +- red/runtime/nodes/context/memory.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/red/runtime/nodes/context/localfilesystem.js b/red/runtime/nodes/context/localfilesystem.js index 730f0020c..914b62995 100644 --- a/red/runtime/nodes/context/localfilesystem.js +++ b/red/runtime/nodes/context/localfilesystem.js @@ -139,7 +139,7 @@ LocalFileSystem.prototype.clean = function(activeNodes){ var self = this; return fs.readdir(self.storageBaseDir).then(function(dirs){ return when.all(dirs.reduce(function(result, item){ - if(item !== "global" && !activeNodes.includes(item)){ + if(item !== "global" && activeNodes.indexOf(item) === -1){ result.push(fs.remove(path.join(self.storageBaseDir,item))); delete self.storages[item]; } diff --git a/red/runtime/nodes/context/memory.js b/red/runtime/nodes/context/memory.js index 1d9711094..422b1b9f2 100644 --- a/red/runtime/nodes/context/memory.js +++ b/red/runtime/nodes/context/memory.js @@ -77,7 +77,7 @@ Memory.prototype.clean = function(activeNodes){ for(var id in this.data){ if(this.data.hasOwnProperty(id) && id !== "global"){ var idParts = id.split(":"); - if(!activeNodes.includes(idParts[0])){ + if(!activeNodes.indexOf(idParts[0])){ delete this.data[id]; } } From f44487338dec72b6eee63874f70bf717c19d7c29 Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Wed, 6 Jun 2018 18:46:32 +0900 Subject: [PATCH 22/32] Fix a wrong statement --- red/runtime/nodes/context/memory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/red/runtime/nodes/context/memory.js b/red/runtime/nodes/context/memory.js index 422b1b9f2..74a8a4380 100644 --- a/red/runtime/nodes/context/memory.js +++ b/red/runtime/nodes/context/memory.js @@ -77,7 +77,7 @@ Memory.prototype.clean = function(activeNodes){ for(var id in this.data){ if(this.data.hasOwnProperty(id) && id !== "global"){ var idParts = id.split(":"); - if(!activeNodes.indexOf(idParts[0])){ + if(activeNodes.indexOf(idParts[0]) === -1){ delete this.data[id]; } } From ed1d34e6788c5a9f7e88d209403cbcaf0c9fb138 Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Thu, 7 Jun 2018 20:17:51 +0900 Subject: [PATCH 23/32] Use fs-extra instead of node-json-db --- package.json | 1 - red/runtime/nodes/context/localfilesystem.js | 108 +++++++++---------- 2 files changed, 52 insertions(+), 57 deletions(-) diff --git a/package.json b/package.json index bd52aa85a..6a9b99fda 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "mqtt": "2.18.0", "multer": "1.3.0", "mustache": "2.3.0", - "node-json-db": "0.7.5", "node-red-node-email": "0.1.*", "node-red-node-feedparser": "0.1.*", "node-red-node-rbe": "0.2.*", diff --git a/red/runtime/nodes/context/localfilesystem.js b/red/runtime/nodes/context/localfilesystem.js index 914b62995..efd869b6e 100644 --- a/red/runtime/nodes/context/localfilesystem.js +++ b/red/runtime/nodes/context/localfilesystem.js @@ -14,25 +14,25 @@ * limitations under the License. **/ -var JsonDB = require('node-json-db'); var fs = require('fs-extra'); var path = require("path"); var when = require("when"); +var util = require("../../util"); -function createStorage(storageBaseDir, scope) { +function getStoragePath(storageBaseDir, scope) { if(scope.indexOf(":") === -1){ if(scope === "global"){ - return new JsonDB(path.join(storageBaseDir,"global",scope), true, true); + return path.join(storageBaseDir,"global",scope); }else{ // scope:flow - return new JsonDB(path.join(storageBaseDir,scope,"flow"), true, true); + return path.join(storageBaseDir,scope,"flow"); } }else{ // scope:local var ids = scope.split(":") - return new JsonDB(path.join(storageBaseDir,ids[1],ids[0]), true, true); + return path.join(storageBaseDir,ids[1],ids[0]); } } -function getStoragePath(config) { +function getBasePath(config) { var base = config.base || "contexts"; var storageBaseDir; if (!config.dir) { @@ -62,10 +62,21 @@ function getStoragePath(config) { return storageBaseDir; } +function loadFile(storagePath){ + return fs.pathExists(storagePath).then(function(exists){ + if(exists === true){ + return fs.readFile(storagePath, "utf8"); + }else{ + return when.resolve(undefined); + } + }).catch(function(err){ + throw when.reject(err); + }); +} + function LocalFileSystem(config){ this.config = config; - this.storageBaseDir = getStoragePath(this.config); - this.storages = {}; + this.storageBaseDir = getBasePath(this.config); } LocalFileSystem.prototype.open = function(){ @@ -76,63 +87,49 @@ LocalFileSystem.prototype.close = function(){ return when.resolve(); } -LocalFileSystem.prototype.get = function (scope, key) { - if(!this.storages[scope]){ - this.storages[scope] = createStorage(this.storageBaseDir ,scope); - } - try{ - this.storages[scope].reload(); - return this.storages[scope].getData("/" + key.replace(/\./g,"/")); - }catch(err){ - if(err.name === "DataError"){ - return undefined; - }else{ - throw err; - } - } -} - -LocalFileSystem.prototype.set = function(scope, key, value) { - if(!this.storages[scope]){ - this.storages[scope] = createStorage(this.storageBaseDir ,scope); - } - if(value){ - this.storages[scope].push("/" + key.replace(/\./g,"/"), value); - }else{ - this.storages[scope].delete("/" + key.replace(/\./g,"/")); - } -} - -LocalFileSystem.prototype.keys = function(scope) { - if(!this.storages[scope]){ - this.storages[scope] = createStorage(this.storageBaseDir ,scope); - } - return Object.keys(this.storages[scope].getData("/")); -} - LocalFileSystem.prototype.getAsync = function(scope, key) { - return when.resolve(this.get(scope, key)); + var storagePath = getStoragePath(this.storageBaseDir ,scope); + return loadFile(storagePath).then(function(data){ + if(data){ + return util.getMessageProperty(JSON.parse(data),key); + }else{ + return undefined + } + }).catch(function(err){ + return when.reject(err); + }); }; LocalFileSystem.prototype.setAsync =function(scope, key, value) { - return when.resolve(this.set(scope, key, value)); + var storagePath = getStoragePath(this.storageBaseDir ,scope); + return loadFile(storagePath).then(function(data){ + var obj = data ? JSON.parse(data) : {} + util.setMessageProperty(obj,key,value); + return obj; + }).then(function(obj){ + var str = JSON.stringify(obj, undefined, 4); + return fs.outputFile(storagePath, str, "utf8"); + }).catch(function(err){ + return when.reject(err); + }); }; LocalFileSystem.prototype.keysAsync = function(scope){ - return when.resolve(this.keys(scope)); + var storagePath = getStoragePath(this.storageBaseDir ,scope); + return loadFile(storagePath).then(function(data){ + if(data){ + return Object.keys(JSON.parse(data)); + }else{ + return [] + } + }).catch(function(err){ + return when.reject(err); + }); }; LocalFileSystem.prototype.delete = function(scope){ - var self = this; - if(this.storages[scope]){ - this.storages[scope].delete("/"); - return fs.remove(this.storages[scope].filename).then(function(){ - delete self.storages[scope]; - return when.resolve(); - }); - }else{ - return when.resolve(); - } + var storagePath = getStoragePath(this.storageBaseDir ,scope); + return fs.remove(storagePath); } LocalFileSystem.prototype.clean = function(activeNodes){ @@ -141,7 +138,6 @@ LocalFileSystem.prototype.clean = function(activeNodes){ return when.all(dirs.reduce(function(result, item){ if(item !== "global" && activeNodes.indexOf(item) === -1){ result.push(fs.remove(path.join(self.storageBaseDir,item))); - delete self.storages[item]; } return result; },[])); From 41a04a2849bbbecbc40e83981fef2ebc125536ee Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Thu, 7 Jun 2018 21:51:11 +0900 Subject: [PATCH 24/32] Add async API to context and add test cases for async --- red/runtime/nodes/context/index.js | 27 +- test/red/runtime/nodes/context/index_spec.js | 264 +++++++------- .../nodes/context/localfilesystem_spec.js | 321 +++++++++++++----- test/red/runtime/nodes/context/memory_spec.js | 30 +- 4 files changed, 420 insertions(+), 222 deletions(-) diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index 02f5a933d..216a24855 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -154,30 +154,45 @@ function createContext(id,seed) { obj.get = function(key) { var keyPath = parseKey(key); var context = getContextStorage(keyPath.storage); - if(key === keyPath.key){ + if(!keyPath.storage){ return context.get(scope, keyPath.key); }else{ - return context.getAsync(scope, keyPath.key); + throw new Error(keyPath.storage + " does not support get(). Use getAsync()"); } }; obj.set = function(key, value) { var keyPath = parseKey(key); var context = getContextStorage(keyPath.storage); - if(key === keyPath.key){ + if(!keyPath.storage){ return context.set(scope, keyPath.key, value); }else{ - return context.setAsync(scope, keyPath.key, value); + throw new Error(keyPath.storage + " does not support set(). Use setAsync()"); } }; obj.keys = function(storage) { var storageName = parseStorage(storage); var context = getContextStorage(storageName); - if(!storage){ + if(!storageName){ return context.keys(scope); }else{ - return context.keysAsync(scope); + throw new Error(storageName + " does not support keys(). Use keysAsync()"); } }; + obj.getAsync = function(key) { + var keyPath = parseKey(key); + var context = getContextStorage(keyPath.storage); + return context.getAsync(scope, keyPath.key); + }; + obj.setAsync = function(key, value) { + var keyPath = parseKey(key); + var context = getContextStorage(keyPath.storage); + return context.setAsync(scope, keyPath.key, value); + }; + obj.keysAsync = function(storage) { + var storageName = parseStorage(storage); + var context = getContextStorage(storageName); + return context.keysAsync(scope); + }; return obj; } diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index c4bfa12ed..021f078ff 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -16,6 +16,7 @@ var should = require("should"); var sinon = require('sinon'); +var when = require("when") var rewire = require("rewire"); var Context = require("../../../../../red/runtime/nodes/context/index"); @@ -33,7 +34,7 @@ describe('context', function() { var context1 = Context.get("1","flowA"); should.not.exist(context1.get("foo")); context1.set("foo","test"); - context1.get("foo").should.eql("test"); + context1.get("foo").should.equal("test"); }); it('stores local property - creates parent properties',function() { var context1 = Context.get("1","flowA"); @@ -56,13 +57,13 @@ describe('context', function() { var context1 = Context.get("1","flowA"); should.not.exist(context1.flow.get("foo")); context1.flow.set("foo","test"); - context1.flow.get("foo").should.eql("test"); + context1.flow.get("foo").should.equal("test"); }); it('stores global property',function() { var context1 = Context.get("1","flowA"); should.not.exist(context1.global.get("foo")); context1.global.set("foo","test"); - context1.global.get("foo").should.eql("test"); + context1.global.get("foo").should.equal("test"); }); it('keeps local context local', function() { @@ -73,7 +74,7 @@ describe('context', function() { should.not.exist(context2.get("foo")); context1.set("foo","test"); - context1.get("foo").should.eql("test"); + context1.get("foo").should.equal("test"); should.not.exist(context2.get("foo")); }); it('flow context accessible to all flow nodes', function() { @@ -84,8 +85,8 @@ describe('context', function() { should.not.exist(context2.flow.get("foo")); context1.flow.set("foo","test"); - context1.flow.get("foo").should.eql("test"); - context2.flow.get("foo").should.eql("test"); + context1.flow.get("foo").should.equal("test"); + context2.flow.get("foo").should.equal("test"); }); it('flow context not shared to nodes on other flows', function() { @@ -96,7 +97,7 @@ describe('context', function() { should.not.exist(context2.flow.get("foo")); context1.flow.set("foo","test"); - context1.flow.get("foo").should.eql("test"); + context1.flow.get("foo").should.equal("test"); should.not.exist(context2.flow.get("foo")); }); @@ -108,15 +109,15 @@ describe('context', function() { should.not.exist(context2.global.get("foo")); context1.global.set("foo","test"); - context1.global.get("foo").should.eql("test"); - context2.global.get("foo").should.eql("test"); + context1.global.get("foo").should.equal("test"); + context2.global.get("foo").should.equal("test"); }); it('deletes context',function() { var context = Context.get("1","flowA"); should.not.exist(context.get("foo")); context.set("foo","abc"); - context.get("foo").should.eql("abc"); + context.get("foo").should.equal("abc"); return Context.delete("1","flowA").then(function(){ context = Context.get("1","flowA"); @@ -134,12 +135,12 @@ describe('context', function() { context.set("foo","bar"); keys = context.keys(); keys.should.have.length(1); - keys[0].should.eql("foo"); + keys[0].should.equal("foo"); context.set("abc.def","bar"); keys = context.keys(); keys.should.have.length(2); - keys[1].should.eql("abc"); + keys[1].should.equal("abc"); }); it('should enumerate only context keys when GlobalContext was given', function() { @@ -148,42 +149,45 @@ describe('context', function() { var context = Context.get("1","flowA"); var keys = context.global.keys("global"); keys.should.have.length(1); - keys[0].should.eql("foo"); + keys[0].should.equal("foo"); }); }); - it('should store data on memory when contextStorage is not defined', function() { + it('should throw error when persistable key is passed', function() { var context = Context.get("1","flow"); - context.set("#nonexist.key1", "val1"); - context.get("#nonexist.key1").should.eql("val1"); - context.flow.set("#nonexist.key2", "val2"); - context.flow.get("#nonexist.key2").should.eql("val2"); - context.global.set("#nonexist.key1", "val3"); - context.global.get("#nonexist.key1").should.eql("val3"); + (function() { + context.set("#nonexist.key1", "val1"); + }).should.throw(); + (function() { + context.get("#nonexist.key1"); + }).should.throw(); + (function() { + context.keys("#nonexist"); + }).should.throw(); }); }); describe('external context storage',function() { var sandbox = sinon.sandbox.create(); - var stubGet = sandbox.stub(); - var stubSet = sandbox.stub(); - var stubKeys = sandbox.stub(); - var stubDelete = sandbox.stub().returns(Promise.resolve()); - var stubClean = sandbox.stub().returns(Promise.resolve()); - var stubOpen = sandbox.stub().returns(Promise.resolve()); - var stubClose = sandbox.stub().returns(Promise.resolve()); - var stubGet2 = sandbox.stub(); - var stubSet2 = sandbox.stub(); - var stubKeys2 = sandbox.stub(); - var stubDelete2 = sandbox.stub().returns(Promise.resolve()); - var stubClean2 = sandbox.stub().returns(Promise.resolve()); - var stubOpen2 = sandbox.stub().returns(Promise.resolve()); - var stubClose2 = sandbox.stub().returns(Promise.resolve()); + var stubGetAsync = sandbox.stub().returns(when.resolve()); + var stubSetAsync = sandbox.stub().returns(when.resolve()); + var stubKeysAsync = sandbox.stub().returns(when.resolve()); + var stubDelete = sandbox.stub().returns(when.resolve()); + var stubClean = sandbox.stub().returns(when.resolve()); + var stubOpen = sandbox.stub().returns(when.resolve()); + var stubClose = sandbox.stub().returns(when.resolve()); + var stubGetAsync2 = sandbox.stub().returns(when.resolve()); + var stubSetAsync2 = sandbox.stub().returns(when.resolve()); + var stubKeysAsync2 = sandbox.stub().returns(when.resolve()); + var stubDelete2 = sandbox.stub().returns(when.resolve()); + var stubClean2 = sandbox.stub().returns(when.resolve()); + var stubOpen2 = sandbox.stub().returns(when.resolve()); + var stubClose2 = sandbox.stub().returns(when.resolve()); var testPlugin = function(config){ function Test(){} - Test.prototype.get = stubGet; - Test.prototype.set = stubSet; - Test.prototype.keys = stubKeys; + Test.prototype.getAsync = stubGetAsync; + Test.prototype.setAsync = stubSetAsync; + Test.prototype.keysAsync = stubKeysAsync; Test.prototype.delete = stubDelete; Test.prototype.clean = stubClean; Test.prototype.open = stubOpen; @@ -192,9 +196,9 @@ describe('context', function() { }; var testPlugin2 = function(config){ function Test2(){} - Test2.prototype.get = stubGet2; - Test2.prototype.set = stubSet2; - Test2.prototype.keys = stubKeys2; + Test2.prototype.getAsync = stubGetAsync2; + Test2.prototype.setAsync = stubSetAsync2; + Test2.prototype.keysAsync = stubKeysAsync2; Test2.prototype.delete = stubDelete2; Test2.prototype.clean = stubClean2; Test2.prototype.open = stubOpen2; @@ -251,24 +255,32 @@ describe('context', function() { }); return Context.load().then(function(){ var context = Context.get("1","flow"); - context.set("##%&.sign","sign1"); - context.get("##%&.sign").should.eql("sign1"); - context.set("#\u3042.file2","file2"); - context.get("#\u3042.file2").should.eql("file2"); - context.set("#1.num","num3"); - context.get("#1.num").should.eql("num3"); + return when.all([ + context.setAsync("##%&.sign","sign1").then(function(){ + return context.getAsync("##%&.sign").should.finally.equal("sign1"); + }), + context.setAsync("#\u3042.file2","file2").then(function(){ + return context.getAsync("#\u3042.file2").should.finally.equal("file2"); + }), + context.setAsync("#1.num","num3").then(function(){ + return context.getAsync("#1.num").should.finally.equal("num3"); + }) + ]); }); }); it('should ignore reserved storage name `_`', function() { Context.init({contextStorage:{_:{module:testPlugin}}}); return Context.load().then(function(){ var context = Context.get("1","flow"); - context.set("#_.foo","bar"); - context.get("#_.foo"); - context.keys("#_"); - stubSet.called.should.be.false(); - stubGet.called.should.be.false(); - stubKeys.called.should.be.false(); + return when.all([ + context.setAsync("#_.foo","bar"), + context.getAsync("#_.foo"), + context.keysAsync("#_") + ]).then(function(){ + stubSetAsync.called.should.be.false(); + stubGetAsync.called.should.be.false(); + stubKeysAsync.called.should.be.false(); + }); }); }); it('should fail when using invalid default context', function(done) { @@ -314,94 +326,106 @@ describe('context', function() { Context.init({contextStorage:contextStorage}); return Context.load().then(function(){ var context = Context.get("1","flow"); - should.not.exist(context.get("#test.foo")); - context.set("#test.foo","test"); - context.get("#test.foo"); - context.keys("#test"); - stubSet.calledWithExactly("1:flow","foo","test").should.be.true(); - stubGet.calledWithExactly("1:flow","foo").should.be.true(); - stubKeys.calledWithExactly("1:flow").should.be.true(); + return when.all([ + context.setAsync("#test.foo","test"), + context.getAsync("#test.foo"), + context.keysAsync("#test") + ]).then(function(){ + stubSetAsync.calledWithExactly("1:flow","foo","test").should.be.true(); + stubGetAsync.calledWithExactly("1:flow","foo").should.be.true(); + stubKeysAsync.calledWithExactly("1:flow").should.be.true(); + }); }); }); it('should store flow property to external context storage',function() { Context.init({contextStorage:contextStorage}); return Context.load().then(function(){ var context = Context.get("1","flow"); - should.not.exist(context.flow.get("#test.foo")); - context.flow.set("#test.foo","test"); - context.flow.get("#test.foo"); - context.flow.keys("#test"); - stubSet.calledWithExactly("flow","foo","test").should.be.true(); - stubGet.calledWithExactly("flow","foo").should.be.true(); - stubKeys.calledWithExactly("flow").should.be.true(); + return when.all([ + context.flow.setAsync("#test.foo","test"), + context.flow.getAsync("#test.foo"), + context.flow.keysAsync("#test") + ]).then(function(){ + stubSetAsync.calledWithExactly("flow","foo","test").should.be.true(); + stubGetAsync.calledWithExactly("flow","foo").should.be.true(); + stubKeysAsync.calledWithExactly("flow").should.be.true(); + }); }); }); it('should store global property to external context storage',function() { Context.init({contextStorage:contextStorage}); return Context.load().then(function(){ var context = Context.get("1","flow"); - should.not.exist(context.global.get("#test.foo")); - context.global.set("#test.foo","test"); - context.global.get("#test.foo"); - context.global.keys("#test"); - stubSet.calledWithExactly("global","foo","test").should.be.true(); - stubGet.calledWithExactly("global","foo").should.be.true(); - stubKeys.calledWithExactly("global").should.be.true(); + return when.all([ + context.global.setAsync("#test.foo","test"), + context.global.getAsync("#test.foo"), + context.global.keysAsync("#test") + ]).then(function(){ + stubSetAsync.calledWithExactly("global","foo","test").should.be.true(); + stubGetAsync.calledWithExactly("global","foo").should.be.true(); + stubKeysAsync.calledWithExactly("global").should.be.true(); + }); }); }); it('should store data to the default context when non-existent context storage was specified', function() { Context.init({contextStorage:contextDefaultStorage}); return Context.load().then(function(){ var context = Context.get("1","flow"); - should.not.exist(context.get("#nonexist.foo")); - context.set("#nonexist.foo","test"); - context.get("#nonexist.foo"); - context.keys("#nonexist"); - stubGet.called.should.be.false(); - stubSet.called.should.be.false(); - stubKeys.called.should.be.false(); - stubSet2.calledWithExactly("1:flow","foo","test").should.be.true(); - stubGet2.calledWithExactly("1:flow","foo").should.be.true(); - stubKeys2.calledWithExactly("1:flow").should.be.true(); + return when.all([ + context.setAsync("#nonexist.foo","test"), + context.getAsync("#nonexist.foo"), + context.keysAsync("#nonexist") + ]).then(function(){ + stubGetAsync.called.should.be.false(); + stubSetAsync.called.should.be.false(); + stubKeysAsync.called.should.be.false(); + stubSetAsync2.calledWithExactly("1:flow","foo","test").should.be.true(); + stubGetAsync2.calledWithExactly("1:flow","foo").should.be.true(); + stubKeysAsync2.calledWithExactly("1:flow").should.be.true(); + }); }); }); it('should use the default context', function() { Context.init({contextStorage:contextDefaultStorage}); return Context.load().then(function(){ var context = Context.get("1","flow"); - should.not.exist(context.get("#default.foo")); - context.set("#default.foo","default"); - context.get("#default.foo"); - context.keys("#default"); - stubGet.called.should.be.false(); - stubSet.called.should.be.false(); - stubKeys.called.should.be.false(); - stubSet2.calledWithExactly("1:flow","foo","default").should.be.true(); - stubGet2.calledWithExactly("1:flow","foo").should.be.true(); - stubKeys2.calledWithExactly("1:flow").should.be.true(); + return when.all([ + context.setAsync("#default.foo","default"), + context.getAsync("#default.foo"), + context.keysAsync("#default") + ]).then(function(){ + stubGetAsync.called.should.be.false(); + stubSetAsync.called.should.be.false(); + stubKeysAsync.called.should.be.false(); + stubSetAsync2.calledWithExactly("1:flow","foo","default").should.be.true(); + stubGetAsync2.calledWithExactly("1:flow","foo").should.be.true(); + stubKeysAsync2.calledWithExactly("1:flow").should.be.true(); + }); }); }); it('should use the alias of default context', function() { Context.init({contextStorage:contextDefaultStorage}); return Context.load().then(function(){ var context = Context.get("1","flow"); - should.not.exist(context.get("#.foo")); - context.set("#.foo","alias"); - context.get("#.foo"); - context.keys("#"); - stubGet.called.should.be.false(); - stubSet.called.should.be.false(); - stubKeys.called.should.be.false(); - stubSet2.calledWithExactly("1:flow","foo","alias").should.be.true(); - stubGet2.calledWithExactly("1:flow","foo").should.be.true(); - stubKeys2.calledWithExactly("1:flow").should.be.true(); + return when.all([ + context.setAsync("#.foo","alias"), + context.getAsync("#.foo"), + context.keysAsync("#") + ]).then(function(){ + stubGetAsync.called.should.be.false(); + stubSetAsync.called.should.be.false(); + stubKeysAsync.called.should.be.false(); + stubSetAsync2.calledWithExactly("1:flow","foo","alias").should.be.true(); + stubGetAsync2.calledWithExactly("1:flow","foo").should.be.true(); + stubKeysAsync2.calledWithExactly("1:flow").should.be.true(); + }); }); }); it('should throw an error using undefined storage for local context', function(done) { Context.init({contextStorage:contextStorage}); Context.load().then(function(){ var context = Context.get("1","flow"); - context.get("#nonexist.local"); + context.getAsync("#nonexist.local"); should.fail(null, null, "An error was not thrown using undefined storage for local context"); }).catch(function(err) { if (err.name === "ContextError") { @@ -415,7 +439,7 @@ describe('context', function() { Context.init({contextStorage:contextStorage}); Context.load().then(function(){ var context = Context.get("1","flow"); - context.flow.set("#nonexist.flow"); + context.flow.setAsync("#nonexist.flow"); should.fail(null, null, "An error was not thrown using undefined storage for flow context"); }).catch(function(err) { if (err.name === "ContextError") { @@ -464,32 +488,36 @@ describe('context', function() { return Context.close(); }); it('should work correctly with the valid key name',function() { - context.set("#memory.azAZ09#_","valid"); - context.get("#memory.azAZ09#_").should.eql("valid"); - context.set("#memory.a.b","ab"); - context.get("#memory.a.b").should.eql("ab"); + return when.all([ + context.setAsync("#memory.azAZ09#_","valid"), + context.setAsync("#memory.a.b","ab") + ]).then(function(){ + context.getAsync("#memory.azAZ09#_").should.finally.equal("valid"); + context.getAsync("#memory.a.b").should.finally.equal("ab"); + }); }); it('should treat the key name without dot as a normal context',function() { - context.set("#memory","normal"); - context.get("#memory").should.eql("normal"); + return context.setAsync("#memory","normal").then(function(){ + return context.getAsync("#memory").should.finally.equal("normal"); + }); }); it('should fail when specifying invalid characters',function() { (function() { - context.set("#memory.a.-","invalid1"); + context.setAsync("#memory.a.-","invalid1"); }).should.throw(); (function() { - context.set("#memory.'abc","invalid2"); + context.setAsync("#memory.'abc","invalid2"); }).should.throw(); }); it('should fail when specifying unnecesary space characters for key name',function() { (function() { - context.set("# memory.space","space1"); + context.setAsync("# memory.space","space1"); }).should.throw(); (function() { - context.set("#memory .space","space2"); + context.setAsync("#memory .space","space2"); }).should.throw(); (function() { - context.set("#memory. space","space3"); + context.setAsync("#memory. space","space3"); }).should.throw(); }); }); @@ -500,8 +528,8 @@ describe('context', function() { function returnModuleAndKey(input, expectedModule, expectedKey) { var result = parseKey(input); - result.storage.should.eql(expectedModule); - result.key.should.eql(expectedKey); + result.storage.should.equal(expectedModule); + result.key.should.equal(expectedKey); } it('should return module and key', function() { diff --git a/test/red/runtime/nodes/context/localfilesystem_spec.js b/test/red/runtime/nodes/context/localfilesystem_spec.js index 77a6c440d..f90bb53d9 100644 --- a/test/red/runtime/nodes/context/localfilesystem_spec.js +++ b/test/red/runtime/nodes/context/localfilesystem_spec.js @@ -38,120 +38,275 @@ describe('localfilesystem',function() { }); }); - describe('#get/set',function() { + describe('#getAsync/setAsync',function() { it('should store property',function() { - should.not.exist(context.get("nodeX","foo")); - context.set("nodeX","foo","test"); - context.get("nodeX","foo").should.eql("test"); + return context.getAsync("nodeX","foo").should.be.finally.undefined() + .then(function(){ + return context.setAsync("nodeX","foo","test"); + }).then(function(){ + return context.getAsync("nodeX","foo").should.be.finally.equal("test"); + }); }); it('should store property - creates parent properties',function() { - context.set("nodeX","foo.bar","test"); - context.get("nodeX","foo").should.eql({bar:"test"}); + return context.setAsync("nodeX","foo.bar","test").then(function(){ + return context.getAsync("nodeX","foo").should.be.finally.eql({bar:"test"}); + }); }); it('should delete property',function() { - context.set("nodeX","foo.abc.bar1","test1"); - context.set("nodeX","foo.abc.bar2","test2"); - context.get("nodeX","foo.abc").should.eql({bar1:"test1",bar2:"test2"}); - context.set("nodeX","foo.abc.bar1",undefined); - context.get("nodeX","foo.abc").should.eql({bar2:"test2"}); - context.set("nodeX","foo.abc",undefined); - should.not.exist(context.get("nodeX","foo.abc")); - context.set("nodeX","foo",undefined); - should.not.exist(context.get("nodeX","foo")); + return context.setAsync("nodeX","foo.abc.bar1","test1") + .then(function(){ + return context.setAsync("nodeX","foo.abc.bar2","test2") + }).then(function(){ + return context.getAsync("nodeX","foo.abc").should.be.finally.eql({bar1:"test1",bar2:"test2"}); + }).then(function(){ + return context.setAsync("nodeX","foo.abc.bar1",undefined).then(function(){ + return context.getAsync("nodeX","foo.abc").should.be.finally.eql({bar2:"test2"}); + }); + }).then(function(){ + return context.setAsync("nodeX","foo.abc",undefined).then(function(){ + return context.getAsync("nodeX","foo.abc").should.be.finally.undefined(); + }); + }).then(function(){ + return context.setAsync("nodeX","foo",undefined).then(function(){ + return context.getAsync("nodeX","foo").should.be.finally.undefined(); + }); + }); }); it('should not shared context with other scope', function() { - should.not.exist(context.get("nodeX","foo")); - should.not.exist(context.get("nodeY","foo")); - context.set("nodeX","foo","testX"); - context.set("nodeY","foo","testY"); + return when.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), + context.getAsync("nodeY","foo").should.be.finally.undefined() + ]).then(function(){ + return when.all([context.setAsync("nodeX","foo","testX"), + context.setAsync("nodeY","foo","testY")]) + }).then(function(){ + return when.all([context.getAsync("nodeX","foo").should.be.finally.equal("testX"), + context.getAsync("nodeY","foo").should.be.finally.equal("testY")]); + }); + }); - context.get("nodeX","foo").should.eql("testX"); - context.get("nodeY","foo").should.eql("testY"); + it('should store string',function() { + return context.getAsync("nodeX","foo").should.be.finally.undefined() + .then(function(){ + return context.setAsync("nodeX","foo","bar"); + }).then(function(){ + return context.getAsync("nodeX","foo") + }).then(function(result){ + result.should.be.String(); + result.should.be.equal("bar"); + }).then(function(){ + return context.setAsync("nodeX","foo","1"); + }).then(function(){ + return context.getAsync("nodeX","foo") + }).then(function(result){ + result.should.be.String(); + result.should.be.equal("1"); + }); + }); + + it('should store number',function() { + return context.getAsync("nodeX","foo").should.be.finally.undefined() + .then(function(){ + return context.setAsync("nodeX","foo",1); + }).then(function(){ + return context.getAsync("nodeX","foo") + }).then(function(result){ + result.should.be.Number(); + result.should.be.equal(1); + }); + }); + + it('should store null',function() { + return context.getAsync("nodeX","foo").should.be.finally.undefined() + .then(function(){ + return context.setAsync("nodeX","foo",null); + }).then(function(){ + return context.getAsync("nodeX","foo").should.be.finally.null(); + }); + }); + + it('should store boolean',function() { + return context.getAsync("nodeX","foo").should.be.finally.undefined() + .then(function(){ + return context.setAsync("nodeX","foo",true); + }).then(function(){ + return context.getAsync("nodeX","foo").should.be.finally.Boolean().and.true(); + }).then(function(){ + return context.setAsync("nodeX","foo",false); + }).then(function(){ + return context.getAsync("nodeX","foo").should.be.finally.Boolean().and.false(); + }); + }); + + it('should store object',function() { + return context.getAsync("nodeX","foo").should.be.finally.undefined() + .then(function(){ + return context.setAsync("nodeX","foo",{obj:"bar"}); + }).then(function(){ + return context.getAsync("nodeX","foo") + }).then(function(result){ + result.should.be.Object(); + result.should.eql({obj:"bar"}); + }); + }); + + it('should store array',function() { + return context.getAsync("nodeX","foo").should.be.finally.undefined() + .then(function(){ + return context.setAsync("nodeX","foo",["a","b","c"]); + }).then(function(){ + return context.getAsync("nodeX","foo") + }).then(function(result){ + result.should.be.Array(); + result.should.eql(["a","b","c"]); + }).then(function(){ + return context.getAsync("nodeX","foo[1]") + }).then(function(result){ + result.should.be.String(); + result.should.equal("b"); + }); + }); + + it('should store array of arrays',function() { + return context.getAsync("nodeX","foo").should.be.finally.undefined() + .then(function(){ + return context.setAsync("nodeX","foo",[["a","b","c"],[1,2,3,4],[true,false]]); + }).then(function(){ + return context.getAsync("nodeX","foo") + }).then(function(result){ + result.should.be.Array(); + result.should.have.length(3); + result[0].should.have.length(3); + result[1].should.have.length(4); + result[2].should.have.length(2); + }).then(function(){ + return context.getAsync("nodeX","foo[1]") + }).then(function(result){ + result.should.be.Array(); + result.should.have.length(4); + result.should.be.eql([1,2,3,4]); + }); + }); + + it('should store array of objects',function() { + return context.getAsync("nodeX","foo").should.be.finally.undefined() + .then(function(){ + return context.setAsync("nodeX","foo",[{obj:"bar1"},{obj:"bar2"},{obj:"bar3"}]); + }).then(function(){ + return context.getAsync("nodeX","foo") + }).then(function(result){ + result.should.be.Array(); + result.should.have.length(3); + result[0].should.be.Object(); + result[1].should.be.Object(); + result[2].should.be.Object(); + }).then(function(){ + return context.getAsync("nodeX","foo[1]") + }).then(function(result){ + result.should.be.Object(); + result.should.be.eql({obj:"bar2"}); + }); }); }); - describe('#keys',function() { + describe('#keysAsync',function() { it('should enumerate context keys', function() { - var keys = context.keys("nodeX"); - keys.should.be.an.Array(); - keys.should.be.empty(); - - context.set("nodeX","foo","bar"); - keys = context.keys("nodeX"); - keys.should.have.length(1); - keys[0].should.eql("foo"); - - context.set("nodeX","abc.def","bar"); - keys = context.keys("nodeX"); - keys.should.have.length(2); - keys[1].should.eql("abc"); + return context.keysAsync("nodeX").then(function(result){ + result.should.be.an.Array(); + result.should.be.empty(); + }).then(function(){ + return context.setAsync("nodeX","foo","bar"); + }).then(function(){ + return context.keysAsync("nodeX").then(function(result){ + result.should.have.length(1); + result[0].should.equal("foo"); + }); + }).then(function(){ + return context.setAsync("nodeX","abc.def","bar"); + }).then(function(){ + return context.keysAsync("nodeX").then(function(result){ + result.should.have.length(2); + result[1].should.equal("abc"); + }); + }); }); it('should enumerate context keys in each scopes', function() { - var keysX = context.keys("nodeX"); - keysX.should.be.an.Array(); - keysX.should.be.empty(); - - var keysY = context.keys("nodeY"); - keysY.should.be.an.Array(); - keysY.should.be.empty(); - - context.set("nodeX","foo","bar"); - context.set("nodeY","hoge","piyo"); - keysX = context.keys("nodeX"); - keysX.should.have.length(1); - keysX[0].should.eql("foo"); - - keysY = context.keys("nodeY"); - keysY.should.have.length(1); - keysY[0].should.eql("hoge"); + return when.all([context.keysAsync("nodeX"), + context.keysAsync("nodeY") + ]).then(function(results){ + results[0].should.be.an.Array(); + results[0].should.be.empty(); + results[1].should.be.an.Array(); + results[1].should.be.empty(); + }).then(function(){ + return when.all([context.setAsync("nodeX","foo","bar"), + context.setAsync("nodeY","hoge","piyo")]); + }).then(function(){ + return when.all([context.keysAsync("nodeX"), + context.keysAsync("nodeY")]); + }).then(function(results){ + results[0].should.have.length(1); + results[0][0].should.equal("foo"); + results[1].should.have.length(1); + results[1][0].should.equal("hoge"); + }); }); }); describe('#delete',function() { it('should delete context',function() { - should.not.exist(context.get("nodeX","foo")); - should.not.exist(context.get("nodeY","foo")); - context.set("nodeX","foo","abc"); - context.set("nodeY","foo","abc"); - context.get("nodeX","foo").should.eql("abc"); - context.get("nodeY","foo").should.eql("abc"); - - return context.delete("nodeX").then(function(){ - should.not.exist(context.get("nodeX","foo")); - should.exist(context.get("nodeY","foo")); - }) + return when.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), + context.getAsync("nodeY","foo").should.be.finally.undefined() + ]).then(function(){ + return when.all([context.setAsync("nodeX","foo","abc"), + context.setAsync("nodeY","foo","abc")]); + }).then(function(){ + return when.all([context.getAsync("nodeX","foo").should.be.finally.equal("abc"), + context.getAsync("nodeY","foo").should.be.finally.equal("abc")]) + }).then(function(){ + return context.delete("nodeX"); + }).then(function(){ + return when.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), + context.getAsync("nodeY","foo").should.be.finally.equal("abc")]); + }); }); }); describe('#clean',function() { it('should clean unnecessary context',function() { - should.not.exist(context.get("nodeX","foo")); - should.not.exist(context.get("nodeY","foo")); - context.set("nodeX","foo","abc"); - context.set("nodeY","foo","abc"); - context.get("nodeX","foo").should.eql("abc"); - context.get("nodeY","foo").should.eql("abc"); - - return context.clean([]).then(function(){ - should.not.exist(context.get("nodeX","foo")); - should.not.exist(context.get("nodeY","foo")); + return when.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), + context.getAsync("nodeY","foo").should.be.finally.undefined() + ]).then(function(values){ + return when.all([context.setAsync("nodeX","foo","abc"), + context.setAsync("nodeY","foo","abc")]); + }).then(function(){ + return when.all([context.getAsync("nodeX","foo").should.be.finally.equal("abc"), + context.getAsync("nodeY","foo").should.be.finally.equal("abc")]) + }).then(function(){ + return context.clean([]); + }).then(function(){ + return when.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), + context.getAsync("nodeY","foo").should.be.finally.undefined()]); }); }); - it('should not clean active context',function() { - should.not.exist(context.get("nodeX","foo")); - should.not.exist(context.get("nodeY","foo")); - context.set("nodeX","foo","abc"); - context.set("nodeY","foo","abc"); - context.get("nodeX","foo").should.eql("abc"); - context.get("nodeY","foo").should.eql("abc"); - return context.clean(["nodeX"]).then(function(){ - should.exist(context.get("nodeX","foo")); - should.not.exist(context.get("nodeY","foo")); + it('should not clean active context',function() { + return when.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), + context.getAsync("nodeY","foo").should.be.finally.undefined() + ]).then(function(){ + return when.all([context.setAsync("nodeX","foo","abc"), + context.setAsync("nodeY","foo","abc")]); + }).then(function(){ + return when.all([context.getAsync("nodeX","foo").should.be.finally.equal("abc"), + context.getAsync("nodeY","foo").should.be.finally.equal("abc")]) + }).then(function(){ + return context.clean(["nodeX"]); + }).then(function(){ + return when.all([context.getAsync("nodeX","foo").should.be.finally.equal("abc"), + context.getAsync("nodeY","foo").should.be.finally.undefined()]); }); }); }); diff --git a/test/red/runtime/nodes/context/memory_spec.js b/test/red/runtime/nodes/context/memory_spec.js index 8da35be19..090c46b41 100644 --- a/test/red/runtime/nodes/context/memory_spec.js +++ b/test/red/runtime/nodes/context/memory_spec.js @@ -35,7 +35,7 @@ describe('memory',function() { it('should store property',function() { should.not.exist(context.get("nodeX","foo")); context.set("nodeX","foo","test"); - context.get("nodeX","foo").should.eql("test"); + context.get("nodeX","foo").should.equal("test"); }); it('should store property - creates parent properties',function() { @@ -61,8 +61,8 @@ describe('memory',function() { context.set("nodeX","foo","testX"); context.set("nodeY","foo","testY"); - context.get("nodeX","foo").should.eql("testX"); - context.get("nodeY","foo").should.eql("testY"); + context.get("nodeX","foo").should.equal("testX"); + context.get("nodeY","foo").should.equal("testY"); }); }); @@ -75,12 +75,12 @@ describe('memory',function() { context.set("nodeX","foo","bar"); keys = context.keys("nodeX"); keys.should.have.length(1); - keys[0].should.eql("foo"); + keys[0].should.equal("foo"); context.set("nodeX","abc.def","bar"); keys = context.keys("nodeX"); keys.should.have.length(2); - keys[1].should.eql("abc"); + keys[1].should.equal("abc"); }); it('should enumerate context keys in each scopes', function() { @@ -96,11 +96,11 @@ describe('memory',function() { context.set("nodeY","hoge","piyo"); keysX = context.keys("nodeX"); keysX.should.have.length(1); - keysX[0].should.eql("foo"); + keysX[0].should.equal("foo"); keysY = context.keys("nodeY"); keysY.should.have.length(1); - keysY[0].should.eql("hoge"); + keysY[0].should.equal("hoge"); }); it('should enumerate only context keys when GlobalContext was given', function() { @@ -114,7 +114,7 @@ describe('memory',function() { context.setGlobalContext(data); keys = context.keys("global"); keys.should.have.length(1); - keys[0].should.eql("foo"); + keys[0].should.equal("foo"); }); }); @@ -124,8 +124,8 @@ describe('memory',function() { should.not.exist(context.get("nodeY","foo")); context.set("nodeX","foo","abc"); context.set("nodeY","foo","abc"); - context.get("nodeX","foo").should.eql("abc"); - context.get("nodeY","foo").should.eql("abc"); + context.get("nodeX","foo").should.equal("abc"); + context.get("nodeY","foo").should.equal("abc"); return context.delete("nodeX").then(function(){ should.not.exist(context.get("nodeX","foo")); @@ -140,8 +140,8 @@ describe('memory',function() { should.not.exist(context.get("nodeY","foo")); context.set("nodeX","foo","abc"); context.set("nodeY","foo","abc"); - context.get("nodeX","foo").should.eql("abc"); - context.get("nodeY","foo").should.eql("abc"); + context.get("nodeX","foo").should.equal("abc"); + context.get("nodeY","foo").should.equal("abc"); return context.clean([]).then(function(){ should.not.exist(context.get("nodeX","foo")); @@ -153,8 +153,8 @@ describe('memory',function() { should.not.exist(context.get("nodeY","foo")); context.set("nodeX","foo","abc"); context.set("nodeY","foo","abc"); - context.get("nodeX","foo").should.eql("abc"); - context.get("nodeY","foo").should.eql("abc"); + context.get("nodeX","foo").should.equal("abc"); + context.get("nodeY","foo").should.equal("abc"); return context.clean(["nodeX"]).then(function(){ should.exist(context.get("nodeX","foo")); @@ -173,7 +173,7 @@ describe('memory',function() { foo: "bar" } context.setGlobalContext(data); - context.get("global","foo").should.eql("bar"); + context.get("global","foo").should.equal("bar"); }); }); }); \ No newline at end of file From c4eae3f130282d3b36c39ea922df9bee3ab49695 Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Fri, 8 Jun 2018 19:26:07 +0900 Subject: [PATCH 25/32] Fix file extension --- red/runtime/nodes/context/localfilesystem.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/red/runtime/nodes/context/localfilesystem.js b/red/runtime/nodes/context/localfilesystem.js index efd869b6e..e85e68e16 100644 --- a/red/runtime/nodes/context/localfilesystem.js +++ b/red/runtime/nodes/context/localfilesystem.js @@ -22,7 +22,7 @@ var util = require("../../util"); function getStoragePath(storageBaseDir, scope) { if(scope.indexOf(":") === -1){ if(scope === "global"){ - return path.join(storageBaseDir,"global",scope); + return path.join(storageBaseDir,"global",scope); }else{ // scope:flow return path.join(storageBaseDir,scope,"flow"); } @@ -89,7 +89,7 @@ LocalFileSystem.prototype.close = function(){ LocalFileSystem.prototype.getAsync = function(scope, key) { var storagePath = getStoragePath(this.storageBaseDir ,scope); - return loadFile(storagePath).then(function(data){ + return loadFile(storagePath + ".json").then(function(data){ if(data){ return util.getMessageProperty(JSON.parse(data),key); }else{ @@ -102,13 +102,13 @@ LocalFileSystem.prototype.getAsync = function(scope, key) { LocalFileSystem.prototype.setAsync =function(scope, key, value) { var storagePath = getStoragePath(this.storageBaseDir ,scope); - return loadFile(storagePath).then(function(data){ + return loadFile(storagePath + ".json").then(function(data){ var obj = data ? JSON.parse(data) : {} util.setMessageProperty(obj,key,value); return obj; }).then(function(obj){ var str = JSON.stringify(obj, undefined, 4); - return fs.outputFile(storagePath, str, "utf8"); + return fs.outputFile(storagePath + ".json", str, {encoding:"utf8",flag:"w+"}); }).catch(function(err){ return when.reject(err); }); @@ -116,7 +116,7 @@ LocalFileSystem.prototype.setAsync =function(scope, key, value) { LocalFileSystem.prototype.keysAsync = function(scope){ var storagePath = getStoragePath(this.storageBaseDir ,scope); - return loadFile(storagePath).then(function(data){ + return loadFile(storagePath + ".json").then(function(data){ if(data){ return Object.keys(JSON.parse(data)); }else{ @@ -129,7 +129,7 @@ LocalFileSystem.prototype.keysAsync = function(scope){ LocalFileSystem.prototype.delete = function(scope){ var storagePath = getStoragePath(this.storageBaseDir ,scope); - return fs.remove(storagePath); + return fs.remove(storagePath + ".json"); } LocalFileSystem.prototype.clean = function(activeNodes){ @@ -148,7 +148,7 @@ LocalFileSystem.prototype.clean = function(activeNodes){ return when.reject(err); } }); - } +} module.exports = function(config){ return new LocalFileSystem(config); From 23b887c30ebde411b0254147f393014e1d71b441 Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Mon, 11 Jun 2018 16:04:27 +0900 Subject: [PATCH 26/32] Add a test case for context/index --- test/red/runtime/nodes/context/index_spec.js | 30 +++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index 021f078ff..9ea38a49b 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -121,7 +121,7 @@ describe('context', function() { return Context.delete("1","flowA").then(function(){ context = Context.get("1","flowA"); - should.not.exist(context.get("foo")); + should.not.exist(context.get("foo")); }); }); @@ -221,6 +221,13 @@ describe('context', function() { config:{} } }; + var contextAlias={ + default: "test", + test:{ + module: testPlugin, + config:{} + } + }; afterEach(function() { sandbox.reset(); @@ -228,7 +235,7 @@ describe('context', function() { return Context.close(); }); }); - + describe('load modules',function(){ it('should call open()', function() { Context.init({contextStorage:contextDefaultStorage}); @@ -421,6 +428,21 @@ describe('context', function() { }); }); }); + it('should use default as the alias of other context', function() { + Context.init({contextStorage:contextAlias}); + return Context.load().then(function(){ + var context = Context.get("1","flow"); + return when.all([ + context.setAsync("#.foo","alias"), + context.getAsync("#.foo"), + context.keysAsync("#") + ]).then(function(){ + stubSetAsync.calledWithExactly("1:flow","foo","alias").should.be.true(); + stubGetAsync.calledWithExactly("1:flow","foo").should.be.true(); + stubKeysAsync.calledWithExactly("1:flow").should.be.true(); + }); + }); + }); it('should throw an error using undefined storage for local context', function(done) { Context.init({contextStorage:contextStorage}); Context.load().then(function(){ @@ -537,8 +559,8 @@ describe('context', function() { returnModuleAndKey("#test.aaa.bbb","test","aaa.bbb"); returnModuleAndKey("#1.234","1","234"); returnModuleAndKey("##test.foo","#test","foo"); - returnModuleAndKey("#test.#foo","test","#foo"); - returnModuleAndKey("#test.#foo.#bar","test","#foo.#bar"); + returnModuleAndKey("#test.#foo","test","#foo"); + returnModuleAndKey("#test.#foo.#bar","test","#foo.#bar"); returnModuleAndKey("#test..foo","test",".foo"); returnModuleAndKey("#test..","test","."); returnModuleAndKey("#te-_st.aaa","te-_st","aaa"); From dd81d947fcac39aa94ad1e0a2d5dd1edcfad51b3 Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Wed, 20 Jun 2018 19:50:55 +0900 Subject: [PATCH 27/32] Use native Promise instead of when.js --- red/runtime/nodes/context/index.js | 15 ++-- red/runtime/nodes/context/localfilesystem.js | 21 +++-- red/runtime/nodes/context/memory.js | 13 ++-- test/red/runtime/nodes/context/index_spec.js | 49 ++++++------ .../nodes/context/localfilesystem_spec.js | 77 +++++++++---------- 5 files changed, 85 insertions(+), 90 deletions(-) diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index 216a24855..4ea367185 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -16,7 +16,6 @@ var clone = require("clone"); var log = require("../../log"); -var when = require("when"); var settings; var contexts = {}; @@ -59,14 +58,14 @@ function load() { try{ plugin = require("./"+plugins[pluginName].module); }catch(err){ - return when.reject(new Error(log._("context.error-module-not-loaded", {module:plugins[pluginName].module}))); + return Promise.reject(new Error(log._("context.error-module-not-loaded", {module:plugins[pluginName].module}))); } } else { plugin = plugins[pluginName].module; } externalContexts[pluginName] = plugin(config); }else{ - return when.reject(new Error(log._("context.error-module-not-defined", {storage:pluginName}))); + return Promise.reject(new Error(log._("context.error-module-not-defined", {storage:pluginName}))); } } for(var plugin in externalContexts){ @@ -78,10 +77,10 @@ function load() { if(externalContexts.hasOwnProperty(plugins["default"])){ externalContexts["default"] = externalContexts[plugins["default"]]; }else{ - return when.reject(new Error(log._("context.error-invalid-default-module", {storage:plugins["default"]}))); + return Promise.reject(new Error(log._("context.error-invalid-default-module", {storage:plugins["default"]}))); } } - return when.all(promises); + return Promise.all(promises); } else { noContextStorage = true; return externalContexts["_"].open(); @@ -224,7 +223,7 @@ function deleteContext(id,flowId) { delete contexts[contextId]; return externalContexts["_"].delete(contextId); }else{ - return when.resolve(); + return Promise.resolve(); } } @@ -243,7 +242,7 @@ function clean(flowConfig) { } } } - return when.all(promises); + return Promise.all(promises); } function close() { @@ -253,7 +252,7 @@ function close() { promises.push(externalContexts[plugin].close()); } } - return when.all(promises); + return Promise.all(promises); } module.exports = { diff --git a/red/runtime/nodes/context/localfilesystem.js b/red/runtime/nodes/context/localfilesystem.js index e85e68e16..b49b2dacf 100644 --- a/red/runtime/nodes/context/localfilesystem.js +++ b/red/runtime/nodes/context/localfilesystem.js @@ -16,7 +16,6 @@ var fs = require('fs-extra'); var path = require("path"); -var when = require("when"); var util = require("../../util"); function getStoragePath(storageBaseDir, scope) { @@ -67,10 +66,10 @@ function loadFile(storagePath){ if(exists === true){ return fs.readFile(storagePath, "utf8"); }else{ - return when.resolve(undefined); + return Promise.resolve(undefined); } }).catch(function(err){ - throw when.reject(err); + throw Promise.reject(err); }); } @@ -80,11 +79,11 @@ function LocalFileSystem(config){ } LocalFileSystem.prototype.open = function(){ - return when.resolve(); + return Promise.resolve(); } LocalFileSystem.prototype.close = function(){ - return when.resolve(); + return Promise.resolve(); } LocalFileSystem.prototype.getAsync = function(scope, key) { @@ -96,7 +95,7 @@ LocalFileSystem.prototype.getAsync = function(scope, key) { return undefined } }).catch(function(err){ - return when.reject(err); + return Promise.reject(err); }); }; @@ -110,7 +109,7 @@ LocalFileSystem.prototype.setAsync =function(scope, key, value) { var str = JSON.stringify(obj, undefined, 4); return fs.outputFile(storagePath + ".json", str, {encoding:"utf8",flag:"w+"}); }).catch(function(err){ - return when.reject(err); + return Promise.reject(err); }); }; @@ -123,7 +122,7 @@ LocalFileSystem.prototype.keysAsync = function(scope){ return [] } }).catch(function(err){ - return when.reject(err); + return Promise.reject(err); }); }; @@ -135,7 +134,7 @@ LocalFileSystem.prototype.delete = function(scope){ LocalFileSystem.prototype.clean = function(activeNodes){ var self = this; return fs.readdir(self.storageBaseDir).then(function(dirs){ - return when.all(dirs.reduce(function(result, item){ + return Promise.all(dirs.reduce(function(result, item){ if(item !== "global" && activeNodes.indexOf(item) === -1){ result.push(fs.remove(path.join(self.storageBaseDir,item))); } @@ -143,9 +142,9 @@ LocalFileSystem.prototype.clean = function(activeNodes){ },[])); }).catch(function(err){ if(err.code == 'ENOENT') { - return when.resolve(); + return Promise.resolve(); }else{ - return when.reject(err); + return Promise.reject(err); } }); } diff --git a/red/runtime/nodes/context/memory.js b/red/runtime/nodes/context/memory.js index 74a8a4380..ae9375f7b 100644 --- a/red/runtime/nodes/context/memory.js +++ b/red/runtime/nodes/context/memory.js @@ -15,18 +15,17 @@ **/ var util = require("../../util"); -var when = require("when"); function Memory(config){ this.data = {}; } Memory.prototype.open = function(){ - return when.resolve(); + return Promise.resolve(); }; Memory.prototype.close = function(){ - return when.resolve(); + return Promise.resolve(); }; Memory.prototype.get = function(scope, key) { @@ -46,7 +45,7 @@ Memory.prototype.set =function(scope, key, value) { Memory.prototype.keys = function(scope){ if(!this.data[scope]){ return []; - } + } if (scope !== "global") { return Object.keys(this.data[scope]); } else { @@ -57,15 +56,15 @@ Memory.prototype.keys = function(scope){ }; Memory.prototype.getAsync = function(scope, key) { - return when.resolve(this.get(scope, key)); + return Promise.resolve(this.get(scope, key)); }; Memory.prototype.setAsync =function(scope, key, value) { - return when.resolve(this.set(scope, key, value)); + return Promise.resolve(this.set(scope, key, value)); }; Memory.prototype.keysAsync = function(scope){ - return when.resolve(this.keys(scope)); + return Promise.resolve(this.keys(scope)); }; Memory.prototype.delete = function(scope){ diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index 9ea38a49b..65513374c 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -16,7 +16,6 @@ var should = require("should"); var sinon = require('sinon'); -var when = require("when") var rewire = require("rewire"); var Context = require("../../../../../red/runtime/nodes/context/index"); @@ -169,20 +168,20 @@ describe('context', function() { describe('external context storage',function() { var sandbox = sinon.sandbox.create(); - var stubGetAsync = sandbox.stub().returns(when.resolve()); - var stubSetAsync = sandbox.stub().returns(when.resolve()); - var stubKeysAsync = sandbox.stub().returns(when.resolve()); - var stubDelete = sandbox.stub().returns(when.resolve()); - var stubClean = sandbox.stub().returns(when.resolve()); - var stubOpen = sandbox.stub().returns(when.resolve()); - var stubClose = sandbox.stub().returns(when.resolve()); - var stubGetAsync2 = sandbox.stub().returns(when.resolve()); - var stubSetAsync2 = sandbox.stub().returns(when.resolve()); - var stubKeysAsync2 = sandbox.stub().returns(when.resolve()); - var stubDelete2 = sandbox.stub().returns(when.resolve()); - var stubClean2 = sandbox.stub().returns(when.resolve()); - var stubOpen2 = sandbox.stub().returns(when.resolve()); - var stubClose2 = sandbox.stub().returns(when.resolve()); + var stubGetAsync = sandbox.stub().returns(Promise.resolve()); + var stubSetAsync = sandbox.stub().returns(Promise.resolve()); + var stubKeysAsync = sandbox.stub().returns(Promise.resolve()); + var stubDelete = sandbox.stub().returns(Promise.resolve()); + var stubClean = sandbox.stub().returns(Promise.resolve()); + var stubOpen = sandbox.stub().returns(Promise.resolve()); + var stubClose = sandbox.stub().returns(Promise.resolve()); + var stubGetAsync2 = sandbox.stub().returns(Promise.resolve()); + var stubSetAsync2 = sandbox.stub().returns(Promise.resolve()); + var stubKeysAsync2 = sandbox.stub().returns(Promise.resolve()); + var stubDelete2 = sandbox.stub().returns(Promise.resolve()); + var stubClean2 = sandbox.stub().returns(Promise.resolve()); + var stubOpen2 = sandbox.stub().returns(Promise.resolve()); + var stubClose2 = sandbox.stub().returns(Promise.resolve()); var testPlugin = function(config){ function Test(){} Test.prototype.getAsync = stubGetAsync; @@ -262,7 +261,7 @@ describe('context', function() { }); return Context.load().then(function(){ var context = Context.get("1","flow"); - return when.all([ + return Promise.all([ context.setAsync("##%&.sign","sign1").then(function(){ return context.getAsync("##%&.sign").should.finally.equal("sign1"); }), @@ -279,7 +278,7 @@ describe('context', function() { Context.init({contextStorage:{_:{module:testPlugin}}}); return Context.load().then(function(){ var context = Context.get("1","flow"); - return when.all([ + return Promise.all([ context.setAsync("#_.foo","bar"), context.getAsync("#_.foo"), context.keysAsync("#_") @@ -333,7 +332,7 @@ describe('context', function() { Context.init({contextStorage:contextStorage}); return Context.load().then(function(){ var context = Context.get("1","flow"); - return when.all([ + return Promise.all([ context.setAsync("#test.foo","test"), context.getAsync("#test.foo"), context.keysAsync("#test") @@ -348,7 +347,7 @@ describe('context', function() { Context.init({contextStorage:contextStorage}); return Context.load().then(function(){ var context = Context.get("1","flow"); - return when.all([ + return Promise.all([ context.flow.setAsync("#test.foo","test"), context.flow.getAsync("#test.foo"), context.flow.keysAsync("#test") @@ -363,7 +362,7 @@ describe('context', function() { Context.init({contextStorage:contextStorage}); return Context.load().then(function(){ var context = Context.get("1","flow"); - return when.all([ + return Promise.all([ context.global.setAsync("#test.foo","test"), context.global.getAsync("#test.foo"), context.global.keysAsync("#test") @@ -378,7 +377,7 @@ describe('context', function() { Context.init({contextStorage:contextDefaultStorage}); return Context.load().then(function(){ var context = Context.get("1","flow"); - return when.all([ + return Promise.all([ context.setAsync("#nonexist.foo","test"), context.getAsync("#nonexist.foo"), context.keysAsync("#nonexist") @@ -396,7 +395,7 @@ describe('context', function() { Context.init({contextStorage:contextDefaultStorage}); return Context.load().then(function(){ var context = Context.get("1","flow"); - return when.all([ + return Promise.all([ context.setAsync("#default.foo","default"), context.getAsync("#default.foo"), context.keysAsync("#default") @@ -414,7 +413,7 @@ describe('context', function() { Context.init({contextStorage:contextDefaultStorage}); return Context.load().then(function(){ var context = Context.get("1","flow"); - return when.all([ + return Promise.all([ context.setAsync("#.foo","alias"), context.getAsync("#.foo"), context.keysAsync("#") @@ -432,7 +431,7 @@ describe('context', function() { Context.init({contextStorage:contextAlias}); return Context.load().then(function(){ var context = Context.get("1","flow"); - return when.all([ + return Promise.all([ context.setAsync("#.foo","alias"), context.getAsync("#.foo"), context.keysAsync("#") @@ -510,7 +509,7 @@ describe('context', function() { return Context.close(); }); it('should work correctly with the valid key name',function() { - return when.all([ + return Promise.all([ context.setAsync("#memory.azAZ09#_","valid"), context.setAsync("#memory.a.b","ab") ]).then(function(){ diff --git a/test/red/runtime/nodes/context/localfilesystem_spec.js b/test/red/runtime/nodes/context/localfilesystem_spec.js index f90bb53d9..a8ecf8cc3 100644 --- a/test/red/runtime/nodes/context/localfilesystem_spec.js +++ b/test/red/runtime/nodes/context/localfilesystem_spec.js @@ -17,7 +17,6 @@ var should = require('should'); var fs = require('fs-extra'); var path = require("path"); -var when = require("when"); var LocalFileSystem = require('../../../../../red/runtime/nodes/context/localfilesystem'); var resourcesDir = path.resolve(path.join(__dirname,"..","resources","context")); @@ -76,14 +75,14 @@ describe('localfilesystem',function() { }); it('should not shared context with other scope', function() { - return when.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), - context.getAsync("nodeY","foo").should.be.finally.undefined() + return Promise.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), + context.getAsync("nodeY","foo").should.be.finally.undefined() ]).then(function(){ - return when.all([context.setAsync("nodeX","foo","testX"), - context.setAsync("nodeY","foo","testY")]) + return Promise.all([context.setAsync("nodeX","foo","testX"), + context.setAsync("nodeY","foo","testY")]) }).then(function(){ - return when.all([context.getAsync("nodeX","foo").should.be.finally.equal("testX"), - context.getAsync("nodeY","foo").should.be.finally.equal("testY")]); + return Promise.all([context.getAsync("nodeX","foo").should.be.finally.equal("testX"), + context.getAsync("nodeY","foo").should.be.finally.equal("testY")]); }); }); @@ -234,19 +233,19 @@ describe('localfilesystem',function() { }); it('should enumerate context keys in each scopes', function() { - return when.all([context.keysAsync("nodeX"), - context.keysAsync("nodeY") + return Promise.all([context.keysAsync("nodeX"), + context.keysAsync("nodeY") ]).then(function(results){ results[0].should.be.an.Array(); results[0].should.be.empty(); results[1].should.be.an.Array(); results[1].should.be.empty(); }).then(function(){ - return when.all([context.setAsync("nodeX","foo","bar"), - context.setAsync("nodeY","hoge","piyo")]); + return Promise.all([context.setAsync("nodeX","foo","bar"), + context.setAsync("nodeY","hoge","piyo")]); }).then(function(){ - return when.all([context.keysAsync("nodeX"), - context.keysAsync("nodeY")]); + return Promise.all([context.keysAsync("nodeX"), + context.keysAsync("nodeY")]); }).then(function(results){ results[0].should.have.length(1); results[0][0].should.equal("foo"); @@ -258,55 +257,55 @@ describe('localfilesystem',function() { describe('#delete',function() { it('should delete context',function() { - return when.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), - context.getAsync("nodeY","foo").should.be.finally.undefined() + return Promise.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), + context.getAsync("nodeY","foo").should.be.finally.undefined() ]).then(function(){ - return when.all([context.setAsync("nodeX","foo","abc"), - context.setAsync("nodeY","foo","abc")]); + return Promise.all([context.setAsync("nodeX","foo","abc"), + context.setAsync("nodeY","foo","abc")]); }).then(function(){ - return when.all([context.getAsync("nodeX","foo").should.be.finally.equal("abc"), - context.getAsync("nodeY","foo").should.be.finally.equal("abc")]) + return Promise.all([context.getAsync("nodeX","foo").should.be.finally.equal("abc"), + context.getAsync("nodeY","foo").should.be.finally.equal("abc")]) }).then(function(){ return context.delete("nodeX"); }).then(function(){ - return when.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), - context.getAsync("nodeY","foo").should.be.finally.equal("abc")]); - }); + return Promise.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), + context.getAsync("nodeY","foo").should.be.finally.equal("abc")]); + }); }); }); describe('#clean',function() { it('should clean unnecessary context',function() { - return when.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), - context.getAsync("nodeY","foo").should.be.finally.undefined() - ]).then(function(values){ - return when.all([context.setAsync("nodeX","foo","abc"), - context.setAsync("nodeY","foo","abc")]); + return Promise.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), + context.getAsync("nodeY","foo").should.be.finally.undefined() + ]).then(function(){ + return Promise.all([context.setAsync("nodeX","foo","abc"), + context.setAsync("nodeY","foo","abc")]); }).then(function(){ - return when.all([context.getAsync("nodeX","foo").should.be.finally.equal("abc"), - context.getAsync("nodeY","foo").should.be.finally.equal("abc")]) + return Promise.all([context.getAsync("nodeX","foo").should.be.finally.equal("abc"), + context.getAsync("nodeY","foo").should.be.finally.equal("abc")]) }).then(function(){ return context.clean([]); }).then(function(){ - return when.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), - context.getAsync("nodeY","foo").should.be.finally.undefined()]); + return Promise.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), + context.getAsync("nodeY","foo").should.be.finally.undefined()]); }); }); it('should not clean active context',function() { - return when.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), - context.getAsync("nodeY","foo").should.be.finally.undefined() + return Promise.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), + context.getAsync("nodeY","foo").should.be.finally.undefined() ]).then(function(){ - return when.all([context.setAsync("nodeX","foo","abc"), - context.setAsync("nodeY","foo","abc")]); + return Promise.all([context.setAsync("nodeX","foo","abc"), + context.setAsync("nodeY","foo","abc")]); }).then(function(){ - return when.all([context.getAsync("nodeX","foo").should.be.finally.equal("abc"), - context.getAsync("nodeY","foo").should.be.finally.equal("abc")]) + return Promise.all([context.getAsync("nodeX","foo").should.be.finally.equal("abc"), + context.getAsync("nodeY","foo").should.be.finally.equal("abc")]) }).then(function(){ return context.clean(["nodeX"]); }).then(function(){ - return when.all([context.getAsync("nodeX","foo").should.be.finally.equal("abc"), - context.getAsync("nodeY","foo").should.be.finally.undefined()]); + return Promise.all([context.getAsync("nodeX","foo").should.be.finally.equal("abc"), + context.getAsync("nodeY","foo").should.be.finally.undefined()]); }); }); }); From e6411d11b1c4bb68bd210f6fd56776bcdf7ab612 Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Wed, 20 Jun 2018 20:00:39 +0900 Subject: [PATCH 28/32] Remove unnecessary context storage APIs and rename context storage APIs --- red/runtime/nodes/context/index.js | 6 +- red/runtime/nodes/context/localfilesystem.js | 6 +- red/runtime/nodes/context/memory.js | 12 -- test/red/runtime/nodes/context/index_spec.js | 176 +++++++++--------- .../nodes/context/localfilesystem_spec.js | 176 +++++++++--------- 5 files changed, 182 insertions(+), 194 deletions(-) diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index 4ea367185..e52308200 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -180,17 +180,17 @@ function createContext(id,seed) { obj.getAsync = function(key) { var keyPath = parseKey(key); var context = getContextStorage(keyPath.storage); - return context.getAsync(scope, keyPath.key); + return context.get(scope, keyPath.key); }; obj.setAsync = function(key, value) { var keyPath = parseKey(key); var context = getContextStorage(keyPath.storage); - return context.setAsync(scope, keyPath.key, value); + return context.set(scope, keyPath.key, value); }; obj.keysAsync = function(storage) { var storageName = parseStorage(storage); var context = getContextStorage(storageName); - return context.keysAsync(scope); + return context.keys(scope); }; return obj; } diff --git a/red/runtime/nodes/context/localfilesystem.js b/red/runtime/nodes/context/localfilesystem.js index b49b2dacf..1d8352f56 100644 --- a/red/runtime/nodes/context/localfilesystem.js +++ b/red/runtime/nodes/context/localfilesystem.js @@ -86,7 +86,7 @@ LocalFileSystem.prototype.close = function(){ return Promise.resolve(); } -LocalFileSystem.prototype.getAsync = function(scope, key) { +LocalFileSystem.prototype.get = function(scope, key) { var storagePath = getStoragePath(this.storageBaseDir ,scope); return loadFile(storagePath + ".json").then(function(data){ if(data){ @@ -99,7 +99,7 @@ LocalFileSystem.prototype.getAsync = function(scope, key) { }); }; -LocalFileSystem.prototype.setAsync =function(scope, key, value) { +LocalFileSystem.prototype.set =function(scope, key, value) { var storagePath = getStoragePath(this.storageBaseDir ,scope); return loadFile(storagePath + ".json").then(function(data){ var obj = data ? JSON.parse(data) : {} @@ -113,7 +113,7 @@ LocalFileSystem.prototype.setAsync =function(scope, key, value) { }); }; -LocalFileSystem.prototype.keysAsync = function(scope){ +LocalFileSystem.prototype.keys = function(scope){ var storagePath = getStoragePath(this.storageBaseDir ,scope); return loadFile(storagePath + ".json").then(function(data){ if(data){ diff --git a/red/runtime/nodes/context/memory.js b/red/runtime/nodes/context/memory.js index ae9375f7b..6ded30d45 100644 --- a/red/runtime/nodes/context/memory.js +++ b/red/runtime/nodes/context/memory.js @@ -55,18 +55,6 @@ Memory.prototype.keys = function(scope){ } }; -Memory.prototype.getAsync = function(scope, key) { - return Promise.resolve(this.get(scope, key)); -}; - -Memory.prototype.setAsync =function(scope, key, value) { - return Promise.resolve(this.set(scope, key, value)); -}; - -Memory.prototype.keysAsync = function(scope){ - return Promise.resolve(this.keys(scope)); -}; - Memory.prototype.delete = function(scope){ delete this.data[scope]; return Promise.resolve(); diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index 65513374c..00ac99fa6 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -168,25 +168,25 @@ describe('context', function() { describe('external context storage',function() { var sandbox = sinon.sandbox.create(); - var stubGetAsync = sandbox.stub().returns(Promise.resolve()); - var stubSetAsync = sandbox.stub().returns(Promise.resolve()); - var stubKeysAsync = sandbox.stub().returns(Promise.resolve()); + var stubGet = sandbox.stub().returns(Promise.resolve()); + var stubSet = sandbox.stub().returns(Promise.resolve()); + var stubKeys = sandbox.stub().returns(Promise.resolve()); var stubDelete = sandbox.stub().returns(Promise.resolve()); var stubClean = sandbox.stub().returns(Promise.resolve()); var stubOpen = sandbox.stub().returns(Promise.resolve()); var stubClose = sandbox.stub().returns(Promise.resolve()); - var stubGetAsync2 = sandbox.stub().returns(Promise.resolve()); - var stubSetAsync2 = sandbox.stub().returns(Promise.resolve()); - var stubKeysAsync2 = sandbox.stub().returns(Promise.resolve()); + var stubGet2 = sandbox.stub().returns(Promise.resolve()); + var stubSet2 = sandbox.stub().returns(Promise.resolve()); + var stubKeys2 = sandbox.stub().returns(Promise.resolve()); var stubDelete2 = sandbox.stub().returns(Promise.resolve()); var stubClean2 = sandbox.stub().returns(Promise.resolve()); var stubOpen2 = sandbox.stub().returns(Promise.resolve()); var stubClose2 = sandbox.stub().returns(Promise.resolve()); var testPlugin = function(config){ function Test(){} - Test.prototype.getAsync = stubGetAsync; - Test.prototype.setAsync = stubSetAsync; - Test.prototype.keysAsync = stubKeysAsync; + Test.prototype.get = stubGet; + Test.prototype.set = stubSet; + Test.prototype.keys = stubKeys; Test.prototype.delete = stubDelete; Test.prototype.clean = stubClean; Test.prototype.open = stubOpen; @@ -195,9 +195,9 @@ describe('context', function() { }; var testPlugin2 = function(config){ function Test2(){} - Test2.prototype.getAsync = stubGetAsync2; - Test2.prototype.setAsync = stubSetAsync2; - Test2.prototype.keysAsync = stubKeysAsync2; + Test2.prototype.get = stubGet2; + Test2.prototype.set = stubSet2; + Test2.prototype.keys = stubKeys2; Test2.prototype.delete = stubDelete2; Test2.prototype.clean = stubClean2; Test2.prototype.open = stubOpen2; @@ -262,14 +262,14 @@ describe('context', function() { return Context.load().then(function(){ var context = Context.get("1","flow"); return Promise.all([ - context.setAsync("##%&.sign","sign1").then(function(){ - return context.getAsync("##%&.sign").should.finally.equal("sign1"); + context.set("##%&.sign","sign1").then(function(){ + return context.get("##%&.sign").should.finally.equal("sign1"); }), - context.setAsync("#\u3042.file2","file2").then(function(){ - return context.getAsync("#\u3042.file2").should.finally.equal("file2"); + context.set("#\u3042.file2","file2").then(function(){ + return context.get("#\u3042.file2").should.finally.equal("file2"); }), - context.setAsync("#1.num","num3").then(function(){ - return context.getAsync("#1.num").should.finally.equal("num3"); + context.set("#1.num","num3").then(function(){ + return context.get("#1.num").should.finally.equal("num3"); }) ]); }); @@ -279,13 +279,13 @@ describe('context', function() { return Context.load().then(function(){ var context = Context.get("1","flow"); return Promise.all([ - context.setAsync("#_.foo","bar"), - context.getAsync("#_.foo"), - context.keysAsync("#_") + context.set("#_.foo","bar"), + context.get("#_.foo"), + context.keys("#_") ]).then(function(){ - stubSetAsync.called.should.be.false(); - stubGetAsync.called.should.be.false(); - stubKeysAsync.called.should.be.false(); + stubSet.called.should.be.false(); + stubGet.called.should.be.false(); + stubKeys.called.should.be.false(); }); }); }); @@ -333,13 +333,13 @@ describe('context', function() { return Context.load().then(function(){ var context = Context.get("1","flow"); return Promise.all([ - context.setAsync("#test.foo","test"), - context.getAsync("#test.foo"), - context.keysAsync("#test") + context.set("#test.foo","test"), + context.get("#test.foo"), + context.keys("#test") ]).then(function(){ - stubSetAsync.calledWithExactly("1:flow","foo","test").should.be.true(); - stubGetAsync.calledWithExactly("1:flow","foo").should.be.true(); - stubKeysAsync.calledWithExactly("1:flow").should.be.true(); + stubSet.calledWithExactly("1:flow","foo","test").should.be.true(); + stubGet.calledWithExactly("1:flow","foo").should.be.true(); + stubKeys.calledWithExactly("1:flow").should.be.true(); }); }); }); @@ -348,13 +348,13 @@ describe('context', function() { return Context.load().then(function(){ var context = Context.get("1","flow"); return Promise.all([ - context.flow.setAsync("#test.foo","test"), - context.flow.getAsync("#test.foo"), - context.flow.keysAsync("#test") + context.flow.set("#test.foo","test"), + context.flow.get("#test.foo"), + context.flow.keys("#test") ]).then(function(){ - stubSetAsync.calledWithExactly("flow","foo","test").should.be.true(); - stubGetAsync.calledWithExactly("flow","foo").should.be.true(); - stubKeysAsync.calledWithExactly("flow").should.be.true(); + stubSet.calledWithExactly("flow","foo","test").should.be.true(); + stubGet.calledWithExactly("flow","foo").should.be.true(); + stubKeys.calledWithExactly("flow").should.be.true(); }); }); }); @@ -363,13 +363,13 @@ describe('context', function() { return Context.load().then(function(){ var context = Context.get("1","flow"); return Promise.all([ - context.global.setAsync("#test.foo","test"), - context.global.getAsync("#test.foo"), - context.global.keysAsync("#test") + context.global.set("#test.foo","test"), + context.global.get("#test.foo"), + context.global.keys("#test") ]).then(function(){ - stubSetAsync.calledWithExactly("global","foo","test").should.be.true(); - stubGetAsync.calledWithExactly("global","foo").should.be.true(); - stubKeysAsync.calledWithExactly("global").should.be.true(); + stubSet.calledWithExactly("global","foo","test").should.be.true(); + stubGet.calledWithExactly("global","foo").should.be.true(); + stubKeys.calledWithExactly("global").should.be.true(); }); }); }); @@ -378,16 +378,16 @@ describe('context', function() { return Context.load().then(function(){ var context = Context.get("1","flow"); return Promise.all([ - context.setAsync("#nonexist.foo","test"), - context.getAsync("#nonexist.foo"), - context.keysAsync("#nonexist") + context.set("#nonexist.foo","test"), + context.get("#nonexist.foo"), + context.keys("#nonexist") ]).then(function(){ - stubGetAsync.called.should.be.false(); - stubSetAsync.called.should.be.false(); - stubKeysAsync.called.should.be.false(); - stubSetAsync2.calledWithExactly("1:flow","foo","test").should.be.true(); - stubGetAsync2.calledWithExactly("1:flow","foo").should.be.true(); - stubKeysAsync2.calledWithExactly("1:flow").should.be.true(); + stubGet.called.should.be.false(); + stubSet.called.should.be.false(); + stubKeys.called.should.be.false(); + stubSet2.calledWithExactly("1:flow","foo","test").should.be.true(); + stubGet2.calledWithExactly("1:flow","foo").should.be.true(); + stubKeys2.calledWithExactly("1:flow").should.be.true(); }); }); }); @@ -396,16 +396,16 @@ describe('context', function() { return Context.load().then(function(){ var context = Context.get("1","flow"); return Promise.all([ - context.setAsync("#default.foo","default"), - context.getAsync("#default.foo"), - context.keysAsync("#default") + context.set("#default.foo","default"), + context.get("#default.foo"), + context.keys("#default") ]).then(function(){ - stubGetAsync.called.should.be.false(); - stubSetAsync.called.should.be.false(); - stubKeysAsync.called.should.be.false(); - stubSetAsync2.calledWithExactly("1:flow","foo","default").should.be.true(); - stubGetAsync2.calledWithExactly("1:flow","foo").should.be.true(); - stubKeysAsync2.calledWithExactly("1:flow").should.be.true(); + stubGet.called.should.be.false(); + stubSet.called.should.be.false(); + stubKeys.called.should.be.false(); + stubSet2.calledWithExactly("1:flow","foo","default").should.be.true(); + stubGet2.calledWithExactly("1:flow","foo").should.be.true(); + stubKeys2.calledWithExactly("1:flow").should.be.true(); }); }); }); @@ -414,16 +414,16 @@ describe('context', function() { return Context.load().then(function(){ var context = Context.get("1","flow"); return Promise.all([ - context.setAsync("#.foo","alias"), - context.getAsync("#.foo"), - context.keysAsync("#") + context.set("#.foo","alias"), + context.get("#.foo"), + context.keys("#") ]).then(function(){ - stubGetAsync.called.should.be.false(); - stubSetAsync.called.should.be.false(); - stubKeysAsync.called.should.be.false(); - stubSetAsync2.calledWithExactly("1:flow","foo","alias").should.be.true(); - stubGetAsync2.calledWithExactly("1:flow","foo").should.be.true(); - stubKeysAsync2.calledWithExactly("1:flow").should.be.true(); + stubGet.called.should.be.false(); + stubSet.called.should.be.false(); + stubKeys.called.should.be.false(); + stubSet2.calledWithExactly("1:flow","foo","alias").should.be.true(); + stubGet2.calledWithExactly("1:flow","foo").should.be.true(); + stubKeys2.calledWithExactly("1:flow").should.be.true(); }); }); }); @@ -432,13 +432,13 @@ describe('context', function() { return Context.load().then(function(){ var context = Context.get("1","flow"); return Promise.all([ - context.setAsync("#.foo","alias"), - context.getAsync("#.foo"), - context.keysAsync("#") + context.set("#.foo","alias"), + context.get("#.foo"), + context.keys("#") ]).then(function(){ - stubSetAsync.calledWithExactly("1:flow","foo","alias").should.be.true(); - stubGetAsync.calledWithExactly("1:flow","foo").should.be.true(); - stubKeysAsync.calledWithExactly("1:flow").should.be.true(); + stubSet.calledWithExactly("1:flow","foo","alias").should.be.true(); + stubGet.calledWithExactly("1:flow","foo").should.be.true(); + stubKeys.calledWithExactly("1:flow").should.be.true(); }); }); }); @@ -446,7 +446,7 @@ describe('context', function() { Context.init({contextStorage:contextStorage}); Context.load().then(function(){ var context = Context.get("1","flow"); - context.getAsync("#nonexist.local"); + context.get("#nonexist.local"); should.fail(null, null, "An error was not thrown using undefined storage for local context"); }).catch(function(err) { if (err.name === "ContextError") { @@ -460,7 +460,7 @@ describe('context', function() { Context.init({contextStorage:contextStorage}); Context.load().then(function(){ var context = Context.get("1","flow"); - context.flow.setAsync("#nonexist.flow"); + context.flow.set("#nonexist.flow"); should.fail(null, null, "An error was not thrown using undefined storage for flow context"); }).catch(function(err) { if (err.name === "ContextError") { @@ -510,35 +510,35 @@ describe('context', function() { }); it('should work correctly with the valid key name',function() { return Promise.all([ - context.setAsync("#memory.azAZ09#_","valid"), - context.setAsync("#memory.a.b","ab") + context.set("#memory.azAZ09#_","valid"), + context.set("#memory.a.b","ab") ]).then(function(){ - context.getAsync("#memory.azAZ09#_").should.finally.equal("valid"); - context.getAsync("#memory.a.b").should.finally.equal("ab"); + context.get("#memory.azAZ09#_").should.finally.equal("valid"); + context.get("#memory.a.b").should.finally.equal("ab"); }); }); it('should treat the key name without dot as a normal context',function() { - return context.setAsync("#memory","normal").then(function(){ - return context.getAsync("#memory").should.finally.equal("normal"); + return context.set("#memory","normal").then(function(){ + return context.get("#memory").should.finally.equal("normal"); }); }); it('should fail when specifying invalid characters',function() { (function() { - context.setAsync("#memory.a.-","invalid1"); + context.set("#memory.a.-","invalid1"); }).should.throw(); (function() { - context.setAsync("#memory.'abc","invalid2"); + context.set("#memory.'abc","invalid2"); }).should.throw(); }); it('should fail when specifying unnecesary space characters for key name',function() { (function() { - context.setAsync("# memory.space","space1"); + context.set("# memory.space","space1"); }).should.throw(); (function() { - context.setAsync("#memory .space","space2"); + context.set("#memory .space","space2"); }).should.throw(); (function() { - context.setAsync("#memory. space","space3"); + context.set("#memory. space","space3"); }).should.throw(); }); }); diff --git a/test/red/runtime/nodes/context/localfilesystem_spec.js b/test/red/runtime/nodes/context/localfilesystem_spec.js index a8ecf8cc3..4a0d0643b 100644 --- a/test/red/runtime/nodes/context/localfilesystem_spec.js +++ b/test/red/runtime/nodes/context/localfilesystem_spec.js @@ -37,68 +37,68 @@ describe('localfilesystem',function() { }); }); - describe('#getAsync/setAsync',function() { + describe('#get/set',function() { it('should store property',function() { - return context.getAsync("nodeX","foo").should.be.finally.undefined() + return context.get("nodeX","foo").should.be.finally.undefined() .then(function(){ - return context.setAsync("nodeX","foo","test"); + return context.set("nodeX","foo","test"); }).then(function(){ - return context.getAsync("nodeX","foo").should.be.finally.equal("test"); + return context.get("nodeX","foo").should.be.finally.equal("test"); }); }); it('should store property - creates parent properties',function() { - return context.setAsync("nodeX","foo.bar","test").then(function(){ - return context.getAsync("nodeX","foo").should.be.finally.eql({bar:"test"}); + return context.set("nodeX","foo.bar","test").then(function(){ + return context.get("nodeX","foo").should.be.finally.eql({bar:"test"}); }); }); it('should delete property',function() { - return context.setAsync("nodeX","foo.abc.bar1","test1") + return context.set("nodeX","foo.abc.bar1","test1") .then(function(){ - return context.setAsync("nodeX","foo.abc.bar2","test2") + return context.set("nodeX","foo.abc.bar2","test2") }).then(function(){ - return context.getAsync("nodeX","foo.abc").should.be.finally.eql({bar1:"test1",bar2:"test2"}); + return context.get("nodeX","foo.abc").should.be.finally.eql({bar1:"test1",bar2:"test2"}); }).then(function(){ - return context.setAsync("nodeX","foo.abc.bar1",undefined).then(function(){ - return context.getAsync("nodeX","foo.abc").should.be.finally.eql({bar2:"test2"}); + return context.set("nodeX","foo.abc.bar1",undefined).then(function(){ + return context.get("nodeX","foo.abc").should.be.finally.eql({bar2:"test2"}); }); }).then(function(){ - return context.setAsync("nodeX","foo.abc",undefined).then(function(){ - return context.getAsync("nodeX","foo.abc").should.be.finally.undefined(); + return context.set("nodeX","foo.abc",undefined).then(function(){ + return context.get("nodeX","foo.abc").should.be.finally.undefined(); }); }).then(function(){ - return context.setAsync("nodeX","foo",undefined).then(function(){ - return context.getAsync("nodeX","foo").should.be.finally.undefined(); + return context.set("nodeX","foo",undefined).then(function(){ + return context.get("nodeX","foo").should.be.finally.undefined(); }); }); }); it('should not shared context with other scope', function() { - return Promise.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), - context.getAsync("nodeY","foo").should.be.finally.undefined() + return Promise.all([context.get("nodeX","foo").should.be.finally.undefined(), + context.get("nodeY","foo").should.be.finally.undefined() ]).then(function(){ - return Promise.all([context.setAsync("nodeX","foo","testX"), - context.setAsync("nodeY","foo","testY")]) + return Promise.all([context.set("nodeX","foo","testX"), + context.set("nodeY","foo","testY")]) }).then(function(){ - return Promise.all([context.getAsync("nodeX","foo").should.be.finally.equal("testX"), - context.getAsync("nodeY","foo").should.be.finally.equal("testY")]); + return Promise.all([context.get("nodeX","foo").should.be.finally.equal("testX"), + context.get("nodeY","foo").should.be.finally.equal("testY")]); }); }); it('should store string',function() { - return context.getAsync("nodeX","foo").should.be.finally.undefined() + return context.get("nodeX","foo").should.be.finally.undefined() .then(function(){ - return context.setAsync("nodeX","foo","bar"); + return context.set("nodeX","foo","bar"); }).then(function(){ - return context.getAsync("nodeX","foo") + return context.get("nodeX","foo") }).then(function(result){ result.should.be.String(); result.should.be.equal("bar"); }).then(function(){ - return context.setAsync("nodeX","foo","1"); + return context.set("nodeX","foo","1"); }).then(function(){ - return context.getAsync("nodeX","foo") + return context.get("nodeX","foo") }).then(function(result){ result.should.be.String(); result.should.be.equal("1"); @@ -106,11 +106,11 @@ describe('localfilesystem',function() { }); it('should store number',function() { - return context.getAsync("nodeX","foo").should.be.finally.undefined() + return context.get("nodeX","foo").should.be.finally.undefined() .then(function(){ - return context.setAsync("nodeX","foo",1); + return context.set("nodeX","foo",1); }).then(function(){ - return context.getAsync("nodeX","foo") + return context.get("nodeX","foo") }).then(function(result){ result.should.be.Number(); result.should.be.equal(1); @@ -118,33 +118,33 @@ describe('localfilesystem',function() { }); it('should store null',function() { - return context.getAsync("nodeX","foo").should.be.finally.undefined() + return context.get("nodeX","foo").should.be.finally.undefined() .then(function(){ - return context.setAsync("nodeX","foo",null); + return context.set("nodeX","foo",null); }).then(function(){ - return context.getAsync("nodeX","foo").should.be.finally.null(); + return context.get("nodeX","foo").should.be.finally.null(); }); }); it('should store boolean',function() { - return context.getAsync("nodeX","foo").should.be.finally.undefined() + return context.get("nodeX","foo").should.be.finally.undefined() .then(function(){ - return context.setAsync("nodeX","foo",true); + return context.set("nodeX","foo",true); }).then(function(){ - return context.getAsync("nodeX","foo").should.be.finally.Boolean().and.true(); + return context.get("nodeX","foo").should.be.finally.Boolean().and.true(); }).then(function(){ - return context.setAsync("nodeX","foo",false); + return context.set("nodeX","foo",false); }).then(function(){ - return context.getAsync("nodeX","foo").should.be.finally.Boolean().and.false(); + return context.get("nodeX","foo").should.be.finally.Boolean().and.false(); }); }); it('should store object',function() { - return context.getAsync("nodeX","foo").should.be.finally.undefined() + return context.get("nodeX","foo").should.be.finally.undefined() .then(function(){ - return context.setAsync("nodeX","foo",{obj:"bar"}); + return context.set("nodeX","foo",{obj:"bar"}); }).then(function(){ - return context.getAsync("nodeX","foo") + return context.get("nodeX","foo") }).then(function(result){ result.should.be.Object(); result.should.eql({obj:"bar"}); @@ -152,16 +152,16 @@ describe('localfilesystem',function() { }); it('should store array',function() { - return context.getAsync("nodeX","foo").should.be.finally.undefined() + return context.get("nodeX","foo").should.be.finally.undefined() .then(function(){ - return context.setAsync("nodeX","foo",["a","b","c"]); + return context.set("nodeX","foo",["a","b","c"]); }).then(function(){ - return context.getAsync("nodeX","foo") + return context.get("nodeX","foo") }).then(function(result){ result.should.be.Array(); result.should.eql(["a","b","c"]); }).then(function(){ - return context.getAsync("nodeX","foo[1]") + return context.get("nodeX","foo[1]") }).then(function(result){ result.should.be.String(); result.should.equal("b"); @@ -169,11 +169,11 @@ describe('localfilesystem',function() { }); it('should store array of arrays',function() { - return context.getAsync("nodeX","foo").should.be.finally.undefined() + return context.get("nodeX","foo").should.be.finally.undefined() .then(function(){ - return context.setAsync("nodeX","foo",[["a","b","c"],[1,2,3,4],[true,false]]); + return context.set("nodeX","foo",[["a","b","c"],[1,2,3,4],[true,false]]); }).then(function(){ - return context.getAsync("nodeX","foo") + return context.get("nodeX","foo") }).then(function(result){ result.should.be.Array(); result.should.have.length(3); @@ -181,7 +181,7 @@ describe('localfilesystem',function() { result[1].should.have.length(4); result[2].should.have.length(2); }).then(function(){ - return context.getAsync("nodeX","foo[1]") + return context.get("nodeX","foo[1]") }).then(function(result){ result.should.be.Array(); result.should.have.length(4); @@ -190,11 +190,11 @@ describe('localfilesystem',function() { }); it('should store array of objects',function() { - return context.getAsync("nodeX","foo").should.be.finally.undefined() + return context.get("nodeX","foo").should.be.finally.undefined() .then(function(){ - return context.setAsync("nodeX","foo",[{obj:"bar1"},{obj:"bar2"},{obj:"bar3"}]); + return context.set("nodeX","foo",[{obj:"bar1"},{obj:"bar2"},{obj:"bar3"}]); }).then(function(){ - return context.getAsync("nodeX","foo") + return context.get("nodeX","foo") }).then(function(result){ result.should.be.Array(); result.should.have.length(3); @@ -202,7 +202,7 @@ describe('localfilesystem',function() { result[1].should.be.Object(); result[2].should.be.Object(); }).then(function(){ - return context.getAsync("nodeX","foo[1]") + return context.get("nodeX","foo[1]") }).then(function(result){ result.should.be.Object(); result.should.be.eql({obj:"bar2"}); @@ -210,22 +210,22 @@ describe('localfilesystem',function() { }); }); - describe('#keysAsync',function() { + describe('#keys',function() { it('should enumerate context keys', function() { - return context.keysAsync("nodeX").then(function(result){ + return context.keys("nodeX").then(function(result){ result.should.be.an.Array(); result.should.be.empty(); }).then(function(){ - return context.setAsync("nodeX","foo","bar"); + return context.set("nodeX","foo","bar"); }).then(function(){ - return context.keysAsync("nodeX").then(function(result){ + return context.keys("nodeX").then(function(result){ result.should.have.length(1); result[0].should.equal("foo"); }); }).then(function(){ - return context.setAsync("nodeX","abc.def","bar"); + return context.set("nodeX","abc.def","bar"); }).then(function(){ - return context.keysAsync("nodeX").then(function(result){ + return context.keys("nodeX").then(function(result){ result.should.have.length(2); result[1].should.equal("abc"); }); @@ -233,19 +233,19 @@ describe('localfilesystem',function() { }); it('should enumerate context keys in each scopes', function() { - return Promise.all([context.keysAsync("nodeX"), - context.keysAsync("nodeY") + return Promise.all([context.keys("nodeX"), + context.keys("nodeY") ]).then(function(results){ results[0].should.be.an.Array(); results[0].should.be.empty(); results[1].should.be.an.Array(); results[1].should.be.empty(); }).then(function(){ - return Promise.all([context.setAsync("nodeX","foo","bar"), - context.setAsync("nodeY","hoge","piyo")]); + return Promise.all([context.set("nodeX","foo","bar"), + context.set("nodeY","hoge","piyo")]); }).then(function(){ - return Promise.all([context.keysAsync("nodeX"), - context.keysAsync("nodeY")]); + return Promise.all([context.keys("nodeX"), + context.keys("nodeY")]); }).then(function(results){ results[0].should.have.length(1); results[0][0].should.equal("foo"); @@ -257,55 +257,55 @@ describe('localfilesystem',function() { describe('#delete',function() { it('should delete context',function() { - return Promise.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), - context.getAsync("nodeY","foo").should.be.finally.undefined() + return Promise.all([context.get("nodeX","foo").should.be.finally.undefined(), + context.get("nodeY","foo").should.be.finally.undefined() ]).then(function(){ - return Promise.all([context.setAsync("nodeX","foo","abc"), - context.setAsync("nodeY","foo","abc")]); + return Promise.all([context.set("nodeX","foo","abc"), + context.set("nodeY","foo","abc")]); }).then(function(){ - return Promise.all([context.getAsync("nodeX","foo").should.be.finally.equal("abc"), - context.getAsync("nodeY","foo").should.be.finally.equal("abc")]) + return Promise.all([context.get("nodeX","foo").should.be.finally.equal("abc"), + context.get("nodeY","foo").should.be.finally.equal("abc")]) }).then(function(){ return context.delete("nodeX"); }).then(function(){ - return Promise.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), - context.getAsync("nodeY","foo").should.be.finally.equal("abc")]); + return Promise.all([context.get("nodeX","foo").should.be.finally.undefined(), + context.get("nodeY","foo").should.be.finally.equal("abc")]); }); }); }); describe('#clean',function() { it('should clean unnecessary context',function() { - return Promise.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), - context.getAsync("nodeY","foo").should.be.finally.undefined() + return Promise.all([context.get("nodeX","foo").should.be.finally.undefined(), + context.get("nodeY","foo").should.be.finally.undefined() ]).then(function(){ - return Promise.all([context.setAsync("nodeX","foo","abc"), - context.setAsync("nodeY","foo","abc")]); + return Promise.all([context.set("nodeX","foo","abc"), + context.set("nodeY","foo","abc")]); }).then(function(){ - return Promise.all([context.getAsync("nodeX","foo").should.be.finally.equal("abc"), - context.getAsync("nodeY","foo").should.be.finally.equal("abc")]) + return Promise.all([context.get("nodeX","foo").should.be.finally.equal("abc"), + context.get("nodeY","foo").should.be.finally.equal("abc")]) }).then(function(){ return context.clean([]); }).then(function(){ - return Promise.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), - context.getAsync("nodeY","foo").should.be.finally.undefined()]); + return Promise.all([context.get("nodeX","foo").should.be.finally.undefined(), + context.get("nodeY","foo").should.be.finally.undefined()]); }); }); it('should not clean active context',function() { - return Promise.all([context.getAsync("nodeX","foo").should.be.finally.undefined(), - context.getAsync("nodeY","foo").should.be.finally.undefined() + return Promise.all([context.get("nodeX","foo").should.be.finally.undefined(), + context.get("nodeY","foo").should.be.finally.undefined() ]).then(function(){ - return Promise.all([context.setAsync("nodeX","foo","abc"), - context.setAsync("nodeY","foo","abc")]); + return Promise.all([context.set("nodeX","foo","abc"), + context.set("nodeY","foo","abc")]); }).then(function(){ - return Promise.all([context.getAsync("nodeX","foo").should.be.finally.equal("abc"), - context.getAsync("nodeY","foo").should.be.finally.equal("abc")]) + return Promise.all([context.get("nodeX","foo").should.be.finally.equal("abc"), + context.get("nodeY","foo").should.be.finally.equal("abc")]) }).then(function(){ return context.clean(["nodeX"]); }).then(function(){ - return Promise.all([context.getAsync("nodeX","foo").should.be.finally.equal("abc"), - context.getAsync("nodeY","foo").should.be.finally.undefined()]); + return Promise.all([context.get("nodeX","foo").should.be.finally.equal("abc"), + context.get("nodeY","foo").should.be.finally.undefined()]); }); }); }); From fd67d084029829a832144a8f8ce9f9f1e32108f4 Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Wed, 20 Jun 2018 20:09:02 +0900 Subject: [PATCH 29/32] Remove unnecessary module and skip persistable context test cases temporally --- package.json | 1 - test/red/runtime/nodes/context/index_spec.js | 67 +------------------- 2 files changed, 1 insertion(+), 67 deletions(-) diff --git a/package.json b/package.json index 6a9b99fda..be2fec970 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,6 @@ "http-proxy": "^1.16.2", "istanbul": "0.4.5", "mocha": "^5.1.1", - "rewire": "3.0.2", "should": "^8.4.0", "sinon": "1.17.7", "stoppable": "^1.0.6", diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index 00ac99fa6..96fbfda70 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -16,7 +16,6 @@ var should = require("should"); var sinon = require('sinon'); -var rewire = require("rewire"); var Context = require("../../../../../red/runtime/nodes/context/index"); describe('context', function() { @@ -166,7 +165,7 @@ describe('context', function() { }); }); - describe('external context storage',function() { + describe.skip('external context storage',function() { var sandbox = sinon.sandbox.create(); var stubGet = sandbox.stub().returns(Promise.resolve()); var stubSet = sandbox.stub().returns(Promise.resolve()); @@ -543,68 +542,4 @@ describe('context', function() { }); }); }); - - describe('#parseKey()', function() { - var parseKey = rewire("../../../../../red/runtime/nodes/context/index").__get__("parseKey"); - - function returnModuleAndKey(input, expectedModule, expectedKey) { - var result = parseKey(input); - result.storage.should.equal(expectedModule); - result.key.should.equal(expectedKey); - } - - it('should return module and key', function() { - returnModuleAndKey("#test.aaa","test","aaa"); - returnModuleAndKey("#test.aaa.bbb","test","aaa.bbb"); - returnModuleAndKey("#1.234","1","234"); - returnModuleAndKey("##test.foo","#test","foo"); - returnModuleAndKey("#test.#foo","test","#foo"); - returnModuleAndKey("#test.#foo.#bar","test","#foo.#bar"); - returnModuleAndKey("#test..foo","test",".foo"); - returnModuleAndKey("#test..","test","."); - returnModuleAndKey("#te-_st.aaa","te-_st","aaa"); - returnModuleAndKey("#te{st.a2","te{st","a2"); - returnModuleAndKey("#te[st.a3","te[st","a3"); - returnModuleAndKey("#te'st.a4","te'st","a4"); - returnModuleAndKey("#te\"st.a5","te\"st","a5"); - }); - - it('should return module as default', function() { - returnModuleAndKey("#default.foo","default","foo"); - returnModuleAndKey("#.foo","default","foo"); - }); - - it('should return only keys', function() { - returnModuleAndKey("test.aaa", "", "test.aaa"); - returnModuleAndKey("test", "", "test"); - returnModuleAndKey("#test", "", "#test"); - }); - - it('should fail with null key', function() { - (function() { - parseKey(""); - }).should.throw(); - - (function() { - parseKey(null); - }).should.throw(); - }); - - it('should fail with space character', function() { - (function() { - parseKey(" #test"); - }).should.throw(); - - (function() { - parseKey("#test .a"); - }).should.throw(); - }); - - it('should fail with empty key', function() { - (function() { - parseKey("#test."); - }).should.throw(); - }); - }); - }); From f2fa26fb07321fe5b33a7653fb2d6ad223ef56ef Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Fri, 22 Jun 2018 17:11:54 +0900 Subject: [PATCH 30/32] Use the callback instead of Promise in context API and remove unnecessary functions --- red/runtime/locales/en-US/runtime.json | 3 - red/runtime/nodes/context/index.js | 94 +--- red/runtime/nodes/context/localfilesystem.js | 43 +- red/runtime/nodes/context/memory.js | 2 +- test/red/runtime/nodes/context/index_spec.js | 302 ++++------- .../nodes/context/localfilesystem_spec.js | 502 ++++++++++-------- 6 files changed, 449 insertions(+), 497 deletions(-) diff --git a/red/runtime/locales/en-US/runtime.json b/red/runtime/locales/en-US/runtime.json index f7a4b8a3d..1413ce523 100644 --- a/red/runtime/locales/en-US/runtime.json +++ b/red/runtime/locales/en-US/runtime.json @@ -161,9 +161,6 @@ "error-module-not-loaded": "'__module__' could not be loaded", "error-module-not-defined": "'module' is not defined in '__storage__' of settings.contextStorage", "error-invalid-default-module": "Invalid storage '__storage__' is specified as a default storage", - "error-key-zero-length": "Invalid property expression: zero-length", - "error-unexpected-space-character": "Invalid property expression: unexpected ' ' at position __index__", - "error-empty-key": "Invalid property expression: key is empty", "error-use-undefined-storage": "Undefined storage '__storage__' is specified" } diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index e52308200..ac52ca152 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -16,6 +16,7 @@ var clone = require("clone"); var log = require("../../log"); +var memory = require("./memory"); var settings; var contexts = {}; @@ -28,7 +29,6 @@ function init(_settings) { externalContexts = {}; // init memory plugin - var memory = require("./memory"); var seed = settings.functionGlobalContext || {}; externalContexts["_"] = memory(); externalContexts["_"].setGlobalContext(seed); @@ -95,43 +95,6 @@ function copySettings(config, settings){ }); } -function parseStorage(key) { - if (!key || key.charAt(0) !== '#') { - return ""; - } else { - var endOfStorageName = key.indexOf("."); - if (endOfStorageName == -1) { - endOfStorageName = key.length; - } - return key.substring(1,endOfStorageName)||"default"; - } -} - -function parseKey(key) { - if (!key) { - throw new Error(log._("context.error-key-zero-length")); - } - var indexSpace = key.indexOf(" "); - if (indexSpace != -1) { - throw new Error(log._("context.error-unexpected-space-character", {index:indexSpace})); - } - var keyPath = { storage: "", key: "" }; - var indexDot = key.indexOf("."); - // The key of "#file" should be treated as a key without persistable context. - if (indexDot != -1) { - keyPath.storage = parseStorage(key); - } - if (keyPath.storage) { - keyPath.key = key.substring(indexDot + 1); - } else { - keyPath.key = key; - } - if(!keyPath.key) { - throw new Error(log._("context.error-empty-key")); - } - return keyPath; -} - function getContextStorage(storage) { if (noContextStorage || !storage) { return externalContexts["_"]; @@ -150,47 +113,30 @@ function createContext(id,seed) { var scope = id; var obj = seed || {}; - obj.get = function(key) { - var keyPath = parseKey(key); - var context = getContextStorage(keyPath.storage); - if(!keyPath.storage){ - return context.get(scope, keyPath.key); - }else{ - throw new Error(keyPath.storage + " does not support get(). Use getAsync()"); + obj.get = function(key, storage, callback) { + if (typeof storage === 'function') { + callback = storage; + storage = "default"; + } else if(typeof storage === "string" && typeof callback !== 'function'){ + throw new Error("Callback must be a function"); } + return getContextStorage(storage).get(scope, key, callback); }; - obj.set = function(key, value) { - var keyPath = parseKey(key); - var context = getContextStorage(keyPath.storage); - if(!keyPath.storage){ - return context.set(scope, keyPath.key, value); - }else{ - throw new Error(keyPath.storage + " does not support set(). Use setAsync()"); + obj.set = function(key, value, storage, callback) { + if (typeof storage === 'function') { + callback = storage; + storage = "default"; } + getContextStorage(storage).set(scope, key, value, callback); }; - obj.keys = function(storage) { - var storageName = parseStorage(storage); - var context = getContextStorage(storageName); - if(!storageName){ - return context.keys(scope); - }else{ - throw new Error(storageName + " does not support keys(). Use keysAsync()"); + obj.keys = function(storage, callback) { + if (typeof storage === 'function') { + callback = storage; + storage = "default"; + } else if(typeof storage === "string" && typeof callback !== 'function'){ + throw new Error("Callback must be a function"); } - }; - obj.getAsync = function(key) { - var keyPath = parseKey(key); - var context = getContextStorage(keyPath.storage); - return context.get(scope, keyPath.key); - }; - obj.setAsync = function(key, value) { - var keyPath = parseKey(key); - var context = getContextStorage(keyPath.storage); - return context.set(scope, keyPath.key, value); - }; - obj.keysAsync = function(storage) { - var storageName = parseStorage(storage); - var context = getContextStorage(storageName); - return context.keys(scope); + return getContextStorage(storage).keys(scope, callback); }; return obj; } diff --git a/red/runtime/nodes/context/localfilesystem.js b/red/runtime/nodes/context/localfilesystem.js index 1d8352f56..fed8ae959 100644 --- a/red/runtime/nodes/context/localfilesystem.js +++ b/red/runtime/nodes/context/localfilesystem.js @@ -86,43 +86,52 @@ LocalFileSystem.prototype.close = function(){ return Promise.resolve(); } -LocalFileSystem.prototype.get = function(scope, key) { +LocalFileSystem.prototype.get = function(scope, key, callback) { + if(typeof callback !== "function"){ + throw new Error("Callback must be a function"); + } var storagePath = getStoragePath(this.storageBaseDir ,scope); - return loadFile(storagePath + ".json").then(function(data){ + loadFile(storagePath + ".json").then(function(data){ if(data){ - return util.getMessageProperty(JSON.parse(data),key); + callback(null, util.getMessageProperty(JSON.parse(data),key)); }else{ - return undefined + callback(null, undefined); } }).catch(function(err){ - return Promise.reject(err); + callback(err); }); }; -LocalFileSystem.prototype.set =function(scope, key, value) { +LocalFileSystem.prototype.set =function(scope, key, value, callback) { var storagePath = getStoragePath(this.storageBaseDir ,scope); - return loadFile(storagePath + ".json").then(function(data){ + loadFile(storagePath + ".json").then(function(data){ var obj = data ? JSON.parse(data) : {} util.setMessageProperty(obj,key,value); - return obj; - }).then(function(obj){ - var str = JSON.stringify(obj, undefined, 4); - return fs.outputFile(storagePath + ".json", str, {encoding:"utf8",flag:"w+"}); + return fs.outputFile(storagePath + ".json", JSON.stringify(obj, undefined, 4), "utf8"); + }).then(function(){ + if(typeof callback === "function"){ + callback(null); + } }).catch(function(err){ - return Promise.reject(err); + if(typeof callback === "function"){ + callback(err); + } }); }; -LocalFileSystem.prototype.keys = function(scope){ +LocalFileSystem.prototype.keys = function(scope, callback){ + if(typeof callback !== "function"){ + throw new Error("Callback must be a function"); + } var storagePath = getStoragePath(this.storageBaseDir ,scope); - return loadFile(storagePath + ".json").then(function(data){ + loadFile(storagePath + ".json").then(function(data){ if(data){ - return Object.keys(JSON.parse(data)); + callback(null, Object.keys(JSON.parse(data))); }else{ - return [] + callback(null, []); } }).catch(function(err){ - return Promise.reject(err); + callback(err); }); }; diff --git a/red/runtime/nodes/context/memory.js b/red/runtime/nodes/context/memory.js index 6ded30d45..3010b4315 100644 --- a/red/runtime/nodes/context/memory.js +++ b/red/runtime/nodes/context/memory.js @@ -50,7 +50,7 @@ Memory.prototype.keys = function(scope){ return Object.keys(this.data[scope]); } else { return Object.keys(this.data[scope]).filter(function (key) { - return key !== "set" && key !== "get" && key !== "keys" && key !== "setAsync" && key !== "getAsync" && key !== "keysAsync"; + return key !== "set" && key !== "get" && key !== "keys"; }); } }; diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index 96fbfda70..bc597d0b9 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -16,6 +16,7 @@ var should = require("should"); var sinon = require('sinon'); +var path = require("path"); var Context = require("../../../../../red/runtime/nodes/context/index"); describe('context', function() { @@ -145,38 +146,26 @@ describe('context', function() { Context.init({functionGlobalContext: {foo:"bar"}}); return Context.load().then(function(){ var context = Context.get("1","flowA"); - var keys = context.global.keys("global"); + var keys = context.global.keys(); keys.should.have.length(1); keys[0].should.equal("foo"); }); }); - - it('should throw error when persistable key is passed', function() { - var context = Context.get("1","flow"); - (function() { - context.set("#nonexist.key1", "val1"); - }).should.throw(); - (function() { - context.get("#nonexist.key1"); - }).should.throw(); - (function() { - context.keys("#nonexist"); - }).should.throw(); - }); }); - describe.skip('external context storage',function() { + describe('external context storage',function() { + var resourcesDir = path.resolve(path.join(__dirname,"..","resources","context")); var sandbox = sinon.sandbox.create(); - var stubGet = sandbox.stub().returns(Promise.resolve()); - var stubSet = sandbox.stub().returns(Promise.resolve()); - var stubKeys = sandbox.stub().returns(Promise.resolve()); + var stubGet = sandbox.stub(); + var stubSet = sandbox.stub(); + var stubKeys = sandbox.stub(); var stubDelete = sandbox.stub().returns(Promise.resolve()); var stubClean = sandbox.stub().returns(Promise.resolve()); var stubOpen = sandbox.stub().returns(Promise.resolve()); var stubClose = sandbox.stub().returns(Promise.resolve()); - var stubGet2 = sandbox.stub().returns(Promise.resolve()); - var stubSet2 = sandbox.stub().returns(Promise.resolve()); - var stubKeys2 = sandbox.stub().returns(Promise.resolve()); + var stubGet2 = sandbox.stub(); + var stubSet2 = sandbox.stub(); + var stubKeys2 = sandbox.stub(); var stubDelete2 = sandbox.stub().returns(Promise.resolve()); var stubClean2 = sandbox.stub().returns(Promise.resolve()); var stubOpen2 = sandbox.stub().returns(Promise.resolve()); @@ -247,45 +236,41 @@ describe('context', function() { return Context.load(); }); it('should load localfilesystem module', function() { - Context.init({contextStorage:{file:{module:"localfilesystem"}}}); + Context.init({contextStorage:{file:{module:"localfilesystem",config:{dir:resourcesDir}}}}); return Context.load(); }); - it('should accept special storage name', function() { + it('should accept special storage name', function(done) { Context.init({ contextStorage:{ - "#%&":{module:"memory"}, - \u3042:{module:"memory"}, - 1:{module:"memory"}, + "#%&":{module:testPlugin}, + \u3042:{module:testPlugin}, + 1:{module:testPlugin}, } }); - return Context.load().then(function(){ + Context.load().then(function(){ var context = Context.get("1","flow"); - return Promise.all([ - context.set("##%&.sign","sign1").then(function(){ - return context.get("##%&.sign").should.finally.equal("sign1"); - }), - context.set("#\u3042.file2","file2").then(function(){ - return context.get("#\u3042.file2").should.finally.equal("file2"); - }), - context.set("#1.num","num3").then(function(){ - return context.get("#1.num").should.finally.equal("num3"); - }) - ]); + var cb = function(){done("An error occurred")} + context.set("sign","sign1","#%&",cb); + context.set("file","file2","\u3042",cb); + context.set("num","num3","1",cb); + stubSet.calledWithExactly("1:flow","sign","sign1",cb).should.be.true(); + stubSet.calledWithExactly("1:flow","file","file2",cb).should.be.true(); + stubSet.calledWithExactly("1:flow","num","num3",cb).should.be.true(); + done(); }); }); - it('should ignore reserved storage name `_`', function() { + it('should ignore reserved storage name `_`', function(done) { Context.init({contextStorage:{_:{module:testPlugin}}}); - return Context.load().then(function(){ + Context.load().then(function(){ var context = Context.get("1","flow"); - return Promise.all([ - context.set("#_.foo","bar"), - context.get("#_.foo"), - context.keys("#_") - ]).then(function(){ - stubSet.called.should.be.false(); - stubGet.called.should.be.false(); - stubKeys.called.should.be.false(); - }); + var cb = function(){done("An error occurred")} + context.set("foo","bar","_",cb); + context.get("foo","_",cb); + context.keys("_",cb); + stubSet.called.should.be.false(); + stubGet.called.should.be.false(); + stubKeys.called.should.be.false(); + done(); }); }); it('should fail when using invalid default context', function(done) { @@ -327,125 +312,119 @@ describe('context', function() { }); describe('store context',function() { - it('should store local property to external context storage',function() { + it('should store local property to external context storage',function(done) { Context.init({contextStorage:contextStorage}); - return Context.load().then(function(){ + var cb = function(){done("An error occurred")} + Context.load().then(function(){ var context = Context.get("1","flow"); - return Promise.all([ - context.set("#test.foo","test"), - context.get("#test.foo"), - context.keys("#test") - ]).then(function(){ - stubSet.calledWithExactly("1:flow","foo","test").should.be.true(); - stubGet.calledWithExactly("1:flow","foo").should.be.true(); - stubKeys.calledWithExactly("1:flow").should.be.true(); - }); + context.set("foo","bar","test",cb); + context.get("foo","test",cb); + context.keys("test",cb); + stubSet.calledWithExactly("1:flow","foo","bar",cb).should.be.true(); + stubGet.calledWithExactly("1:flow","foo",cb).should.be.true(); + stubKeys.calledWithExactly("1:flow",cb).should.be.true(); + done(); }); }); - it('should store flow property to external context storage',function() { + it('should store flow property to external context storage',function(done) { Context.init({contextStorage:contextStorage}); - return Context.load().then(function(){ + Context.load().then(function(){ var context = Context.get("1","flow"); - return Promise.all([ - context.flow.set("#test.foo","test"), - context.flow.get("#test.foo"), - context.flow.keys("#test") - ]).then(function(){ - stubSet.calledWithExactly("flow","foo","test").should.be.true(); - stubGet.calledWithExactly("flow","foo").should.be.true(); - stubKeys.calledWithExactly("flow").should.be.true(); - }); + var cb = function(){done("An error occurred")} + context.flow.set("foo","bar","test",cb); + context.flow.get("foo","test",cb); + context.flow.keys("test",cb); + stubSet.calledWithExactly("flow","foo","bar",cb).should.be.true(); + stubGet.calledWithExactly("flow","foo",cb).should.be.true(); + stubKeys.calledWithExactly("flow",cb).should.be.true(); + done(); }); }); - it('should store global property to external context storage',function() { + it('should store global property to external context storage',function(done) { Context.init({contextStorage:contextStorage}); - return Context.load().then(function(){ + Context.load().then(function(){ var context = Context.get("1","flow"); - return Promise.all([ - context.global.set("#test.foo","test"), - context.global.get("#test.foo"), - context.global.keys("#test") - ]).then(function(){ - stubSet.calledWithExactly("global","foo","test").should.be.true(); - stubGet.calledWithExactly("global","foo").should.be.true(); - stubKeys.calledWithExactly("global").should.be.true(); - }); + var cb = function(){done("An error occurred")} + context.global.set("foo","bar","test",cb); + context.global.get("foo","test",cb); + context.global.keys("test",cb); + stubSet.calledWithExactly("global","foo","bar",cb).should.be.true(); + stubGet.calledWithExactly("global","foo",cb).should.be.true(); + stubKeys.calledWithExactly("global",cb).should.be.true(); + done(); }); }); - it('should store data to the default context when non-existent context storage was specified', function() { + it('should store data to the default context when non-existent context storage was specified', function(done) { Context.init({contextStorage:contextDefaultStorage}); - return Context.load().then(function(){ + Context.load().then(function(){ var context = Context.get("1","flow"); - return Promise.all([ - context.set("#nonexist.foo","test"), - context.get("#nonexist.foo"), - context.keys("#nonexist") - ]).then(function(){ - stubGet.called.should.be.false(); - stubSet.called.should.be.false(); - stubKeys.called.should.be.false(); - stubSet2.calledWithExactly("1:flow","foo","test").should.be.true(); - stubGet2.calledWithExactly("1:flow","foo").should.be.true(); - stubKeys2.calledWithExactly("1:flow").should.be.true(); - }); + var cb = function(){done("An error occurred")} + context.set("foo","bar","nonexist",cb); + context.get("foo","nonexist",cb); + context.keys("nonexist",cb); + stubGet.called.should.be.false(); + stubSet.called.should.be.false(); + stubKeys.called.should.be.false(); + stubSet2.calledWithExactly("1:flow","foo","bar",cb).should.be.true(); + stubGet2.calledWithExactly("1:flow","foo",cb).should.be.true(); + stubKeys2.calledWithExactly("1:flow",cb).should.be.true(); + done(); }); }); - it('should use the default context', function() { + it('should use the default context', function(done) { Context.init({contextStorage:contextDefaultStorage}); - return Context.load().then(function(){ + Context.load().then(function(){ var context = Context.get("1","flow"); - return Promise.all([ - context.set("#default.foo","default"), - context.get("#default.foo"), - context.keys("#default") - ]).then(function(){ - stubGet.called.should.be.false(); - stubSet.called.should.be.false(); - stubKeys.called.should.be.false(); - stubSet2.calledWithExactly("1:flow","foo","default").should.be.true(); - stubGet2.calledWithExactly("1:flow","foo").should.be.true(); - stubKeys2.calledWithExactly("1:flow").should.be.true(); - }); + var cb = function(){done("An error occurred")} + context.set("foo","bar","defaultt",cb); + context.get("foo","default",cb); + context.keys("default",cb); + stubGet.called.should.be.false(); + stubSet.called.should.be.false(); + stubKeys.called.should.be.false(); + stubSet2.calledWithExactly("1:flow","foo","bar",cb).should.be.true(); + stubGet2.calledWithExactly("1:flow","foo",cb).should.be.true(); + stubKeys2.calledWithExactly("1:flow",cb).should.be.true(); + done(); }); }); - it('should use the alias of default context', function() { + it('should use the alias of default context', function(done) { Context.init({contextStorage:contextDefaultStorage}); - return Context.load().then(function(){ + Context.load().then(function(){ var context = Context.get("1","flow"); - return Promise.all([ - context.set("#.foo","alias"), - context.get("#.foo"), - context.keys("#") - ]).then(function(){ - stubGet.called.should.be.false(); - stubSet.called.should.be.false(); - stubKeys.called.should.be.false(); - stubSet2.calledWithExactly("1:flow","foo","alias").should.be.true(); - stubGet2.calledWithExactly("1:flow","foo").should.be.true(); - stubKeys2.calledWithExactly("1:flow").should.be.true(); - }); + var cb = function(){done("An error occurred")} + context.set("foo","alias",cb); + context.get("foo",cb); + context.keys(cb); + stubGet.called.should.be.false(); + stubSet.called.should.be.false(); + stubKeys.called.should.be.false(); + stubSet2.calledWithExactly("1:flow","foo","alias",cb).should.be.true(); + stubGet2.calledWithExactly("1:flow","foo",cb).should.be.true(); + stubKeys2.calledWithExactly("1:flow",cb).should.be.true(); + done(); }); }); - it('should use default as the alias of other context', function() { + it('should use default as the alias of other context', function(done) { Context.init({contextStorage:contextAlias}); - return Context.load().then(function(){ + Context.load().then(function(){ var context = Context.get("1","flow"); - return Promise.all([ - context.set("#.foo","alias"), - context.get("#.foo"), - context.keys("#") - ]).then(function(){ - stubSet.calledWithExactly("1:flow","foo","alias").should.be.true(); - stubGet.calledWithExactly("1:flow","foo").should.be.true(); - stubKeys.calledWithExactly("1:flow").should.be.true(); - }); + var cb = function(){done("An error occurred")} + context.set("foo","alias",cb); + context.get("foo",cb); + context.keys(cb); + stubSet.calledWithExactly("1:flow","foo","alias",cb).should.be.true(); + stubGet.calledWithExactly("1:flow","foo",cb).should.be.true(); + stubKeys.calledWithExactly("1:flow",cb).should.be.true(); + done(); }); }); it('should throw an error using undefined storage for local context', function(done) { Context.init({contextStorage:contextStorage}); Context.load().then(function(){ var context = Context.get("1","flow"); - context.get("#nonexist.local"); + var cb = function(){done("An error occurred")} + context.get("local","nonexist",cb); should.fail(null, null, "An error was not thrown using undefined storage for local context"); }).catch(function(err) { if (err.name === "ContextError") { @@ -459,7 +438,8 @@ describe('context', function() { Context.init({contextStorage:contextStorage}); Context.load().then(function(){ var context = Context.get("1","flow"); - context.flow.set("#nonexist.flow"); + var cb = function(){done("An error occurred")} + context.flow.get("flow","nonexist",cb); should.fail(null, null, "An error was not thrown using undefined storage for flow context"); }).catch(function(err) { if (err.name === "ContextError") { @@ -472,7 +452,7 @@ describe('context', function() { }); describe('delete context',function(){ - it('should not call delete()', function() { + it('should not call delete() when external context storage is used', function() { Context.init({contextStorage:contextDefaultStorage}); return Context.load().then(function(){ Context.get("flowA"); @@ -495,51 +475,5 @@ describe('context', function() { }); }); }); - - describe('key name',function() { - beforeEach(function() { - Context.init({contextStorage:{memory:{module:"memory"}}}); - return Context.load().then(function(){ - context = Context.get("1","flow"); - }); - }); - afterEach(function() { - Context.clean({allNodes:{}}); - return Context.close(); - }); - it('should work correctly with the valid key name',function() { - return Promise.all([ - context.set("#memory.azAZ09#_","valid"), - context.set("#memory.a.b","ab") - ]).then(function(){ - context.get("#memory.azAZ09#_").should.finally.equal("valid"); - context.get("#memory.a.b").should.finally.equal("ab"); - }); - }); - it('should treat the key name without dot as a normal context',function() { - return context.set("#memory","normal").then(function(){ - return context.get("#memory").should.finally.equal("normal"); - }); - }); - it('should fail when specifying invalid characters',function() { - (function() { - context.set("#memory.a.-","invalid1"); - }).should.throw(); - (function() { - context.set("#memory.'abc","invalid2"); - }).should.throw(); - }); - it('should fail when specifying unnecesary space characters for key name',function() { - (function() { - context.set("# memory.space","space1"); - }).should.throw(); - (function() { - context.set("#memory .space","space2"); - }).should.throw(); - (function() { - context.set("#memory. space","space3"); - }).should.throw(); - }); - }); }); }); diff --git a/test/red/runtime/nodes/context/localfilesystem_spec.js b/test/red/runtime/nodes/context/localfilesystem_spec.js index 4a0d0643b..a03d5b24a 100644 --- a/test/red/runtime/nodes/context/localfilesystem_spec.js +++ b/test/red/runtime/nodes/context/localfilesystem_spec.js @@ -24,6 +24,10 @@ var resourcesDir = path.resolve(path.join(__dirname,"..","resources","context")) describe('localfilesystem',function() { var context; + before(function() { + return fs.remove(resourcesDir); + }); + beforeEach(function() { context = LocalFileSystem({dir: resourcesDir}); return context.open(); @@ -38,274 +42,336 @@ describe('localfilesystem',function() { }); describe('#get/set',function() { - it('should store property',function() { - return context.get("nodeX","foo").should.be.finally.undefined() - .then(function(){ - return context.set("nodeX","foo","test"); - }).then(function(){ - return context.get("nodeX","foo").should.be.finally.equal("test"); - }); - }); - - it('should store property - creates parent properties',function() { - return context.set("nodeX","foo.bar","test").then(function(){ - return context.get("nodeX","foo").should.be.finally.eql({bar:"test"}); - }); - }); - - it('should delete property',function() { - return context.set("nodeX","foo.abc.bar1","test1") - .then(function(){ - return context.set("nodeX","foo.abc.bar2","test2") - }).then(function(){ - return context.get("nodeX","foo.abc").should.be.finally.eql({bar1:"test1",bar2:"test2"}); - }).then(function(){ - return context.set("nodeX","foo.abc.bar1",undefined).then(function(){ - return context.get("nodeX","foo.abc").should.be.finally.eql({bar2:"test2"}); - }); - }).then(function(){ - return context.set("nodeX","foo.abc",undefined).then(function(){ - return context.get("nodeX","foo.abc").should.be.finally.undefined(); - }); - }).then(function(){ - return context.set("nodeX","foo",undefined).then(function(){ - return context.get("nodeX","foo").should.be.finally.undefined(); + it('should store property',function(done) { + context.get("nodeX","foo",function(err, value){ + should.not.exist(value); + context.set("nodeX","foo","test",function(err){ + context.get("nodeX","foo",function(err, value){ + value.should.be.equal("test"); + done(); + }); }); }); }); - it('should not shared context with other scope', function() { - return Promise.all([context.get("nodeX","foo").should.be.finally.undefined(), - context.get("nodeY","foo").should.be.finally.undefined() - ]).then(function(){ - return Promise.all([context.set("nodeX","foo","testX"), - context.set("nodeY","foo","testY")]) - }).then(function(){ - return Promise.all([context.get("nodeX","foo").should.be.finally.equal("testX"), - context.get("nodeY","foo").should.be.finally.equal("testY")]); + it('should store property - creates parent properties',function(done) { + context.set("nodeX","foo.bar","test",function(err){ + context.get("nodeX","foo",function(err, value){ + value.should.be.eql({bar:"test"}); + done(); + }); }); }); - it('should store string',function() { - return context.get("nodeX","foo").should.be.finally.undefined() - .then(function(){ - return context.set("nodeX","foo","bar"); - }).then(function(){ - return context.get("nodeX","foo") - }).then(function(result){ - result.should.be.String(); - result.should.be.equal("bar"); - }).then(function(){ - return context.set("nodeX","foo","1"); - }).then(function(){ - return context.get("nodeX","foo") - }).then(function(result){ - result.should.be.String(); - result.should.be.equal("1"); + it('should delete property',function(done) { + context.set("nodeX","foo.abc.bar1","test1",function(err){ + context.set("nodeX","foo.abc.bar2","test2",function(err){ + context.get("nodeX","foo.abc",function(err, value){ + value.should.be.eql({bar1:"test1",bar2:"test2"}); + context.set("nodeX","foo.abc.bar1",undefined,function(err){ + context.get("nodeX","foo.abc",function(err, value){ + value.should.be.eql({bar2:"test2"}); + context.set("nodeX","foo.abc",undefined,function(err){ + context.get("nodeX","foo.abc",function(err, value){ + should.not.exist(value); + context.set("nodeX","foo",undefined,function(err){ + context.get("nodeX","foo",function(err, value){ + should.not.exist(value); + done(); + }); + }); + }); + }); + }); + }); + }); + }); }); }); - it('should store number',function() { - return context.get("nodeX","foo").should.be.finally.undefined() - .then(function(){ - return context.set("nodeX","foo",1); - }).then(function(){ - return context.get("nodeX","foo") - }).then(function(result){ - result.should.be.Number(); - result.should.be.equal(1); + it('should not shared context with other scope', function(done) { + context.get("nodeX","foo",function(err, value){ + should.not.exist(value); + context.get("nodeY","foo",function(err, value){ + should.not.exist(value); + context.set("nodeX","foo","testX",function(err){ + context.set("nodeY","foo","testY",function(err){ + context.get("nodeX","foo",function(err, value){ + value.should.be.equal("testX"), + context.get("nodeY","foo",function(err, value){ + value.should.be.equal("testY"); + done(); + }); + }); + }); + }); + }); }); }); - it('should store null',function() { - return context.get("nodeX","foo").should.be.finally.undefined() - .then(function(){ - return context.set("nodeX","foo",null); - }).then(function(){ - return context.get("nodeX","foo").should.be.finally.null(); + it('should store string',function(done) { + context.get("nodeX","foo",function(err, value){ + should.not.exist(value); + context.set("nodeX","foo","bar",function(err){ + context.get("nodeX","foo",function(err, value){ + value.should.be.String(); + value.should.be.equal("bar"); + context.set("nodeX","foo","1",function(err){ + context.get("nodeX","foo",function(err, value){ + value.should.be.String(); + value.should.be.equal("1"); + done(); + }); + }); + }); + }); }); }); - it('should store boolean',function() { - return context.get("nodeX","foo").should.be.finally.undefined() - .then(function(){ - return context.set("nodeX","foo",true); - }).then(function(){ - return context.get("nodeX","foo").should.be.finally.Boolean().and.true(); - }).then(function(){ - return context.set("nodeX","foo",false); - }).then(function(){ - return context.get("nodeX","foo").should.be.finally.Boolean().and.false(); + it('should store number',function(done) { + context.get("nodeX","foo",function(err, value){ + should.not.exist(value); + context.set("nodeX","foo",1,function(err){ + context.get("nodeX","foo",function(err, value){ + value.should.be.Number(); + value.should.be.equal(1); + done(); + }); + }); }); }); - it('should store object',function() { - return context.get("nodeX","foo").should.be.finally.undefined() - .then(function(){ - return context.set("nodeX","foo",{obj:"bar"}); - }).then(function(){ - return context.get("nodeX","foo") - }).then(function(result){ - result.should.be.Object(); - result.should.eql({obj:"bar"}); + it('should store null',function(done) { + context.get("nodeX","foo",function(err, value){ + should.not.exist(value); + context.set("nodeX","foo",null,function(err){ + context.get("nodeX","foo",function(err, value){ + should(value).be.null(); + done(); + }); + }); }); }); - it('should store array',function() { - return context.get("nodeX","foo").should.be.finally.undefined() - .then(function(){ - return context.set("nodeX","foo",["a","b","c"]); - }).then(function(){ - return context.get("nodeX","foo") - }).then(function(result){ - result.should.be.Array(); - result.should.eql(["a","b","c"]); - }).then(function(){ - return context.get("nodeX","foo[1]") - }).then(function(result){ - result.should.be.String(); - result.should.equal("b"); + it('should store boolean',function(done) { + context.get("nodeX","foo",function(err, value){ + should.not.exist(value); + context.set("nodeX","foo",true,function(err){ + context.get("nodeX","foo",function(err, value){ + value.should.be.Boolean().and.true(); + context.set("nodeX","foo",false,function(err){ + context.get("nodeX","foo",function(err, value){ + value.should.be.Boolean().and.false(); + done(); + }); + }); + }); + }); }); }); - it('should store array of arrays',function() { - return context.get("nodeX","foo").should.be.finally.undefined() - .then(function(){ - return context.set("nodeX","foo",[["a","b","c"],[1,2,3,4],[true,false]]); - }).then(function(){ - return context.get("nodeX","foo") - }).then(function(result){ - result.should.be.Array(); - result.should.have.length(3); - result[0].should.have.length(3); - result[1].should.have.length(4); - result[2].should.have.length(2); - }).then(function(){ - return context.get("nodeX","foo[1]") - }).then(function(result){ - result.should.be.Array(); - result.should.have.length(4); - result.should.be.eql([1,2,3,4]); + it('should store object',function(done) { + context.get("nodeX","foo",function(err, value){ + should.not.exist(value); + context.set("nodeX","foo",{obj:"bar"},function(err){ + context.get("nodeX","foo",function(err, value){ + value.should.be.Object(); + value.should.eql({obj:"bar"}); + done(); + }); + }); }); }); - it('should store array of objects',function() { - return context.get("nodeX","foo").should.be.finally.undefined() - .then(function(){ - return context.set("nodeX","foo",[{obj:"bar1"},{obj:"bar2"},{obj:"bar3"}]); - }).then(function(){ - return context.get("nodeX","foo") - }).then(function(result){ - result.should.be.Array(); - result.should.have.length(3); - result[0].should.be.Object(); - result[1].should.be.Object(); - result[2].should.be.Object(); - }).then(function(){ - return context.get("nodeX","foo[1]") - }).then(function(result){ - result.should.be.Object(); - result.should.be.eql({obj:"bar2"}); + it('should store array',function(done) { + context.get("nodeX","foo",function(err, value){ + should.not.exist(value); + context.set("nodeX","foo",["a","b","c"],function(err){ + context.get("nodeX","foo",function(err, value){ + value.should.be.Array(); + value.should.eql(["a","b","c"]); + context.get("nodeX","foo[1]",function(err, value){ + value.should.be.String(); + value.should.equal("b"); + done(); + }); + }); + }); + }); + }); + + it('should store array of arrays',function(done) { + context.get("nodeX","foo",function(err, value){ + should.not.exist(value); + context.set("nodeX","foo",[["a","b","c"],[1,2,3,4],[true,false]],function(err){ + context.get("nodeX","foo",function(err, value){ + value.should.be.Array(); + value.should.have.length(3); + value[0].should.have.length(3); + value[1].should.have.length(4); + value[2].should.have.length(2); + context.get("nodeX","foo[1]",function(err, value){ + value.should.be.Array(); + value.should.have.length(4); + value.should.be.eql([1,2,3,4]); + done(); + }); + }); + }); + }); + }); + + it('should store array of objects',function(done) { + context.get("nodeX","foo",function(err, value){ + should.not.exist(value); + context.set("nodeX","foo",[{obj:"bar1"},{obj:"bar2"},{obj:"bar3"}],function(err){ + context.get("nodeX","foo",function(err, value){ + value.should.be.Array(); + value.should.have.length(3); + value[0].should.be.Object(); + value[1].should.be.Object(); + value[2].should.be.Object(); + context.get("nodeX","foo[1]",function(err, value){ + value.should.be.Object(); + value.should.be.eql({obj:"bar2"}); + done(); + }); + }); + }); }); }); }); describe('#keys',function() { - it('should enumerate context keys', function() { - return context.keys("nodeX").then(function(result){ - result.should.be.an.Array(); - result.should.be.empty(); - }).then(function(){ - return context.set("nodeX","foo","bar"); - }).then(function(){ - return context.keys("nodeX").then(function(result){ - result.should.have.length(1); - result[0].should.equal("foo"); - }); - }).then(function(){ - return context.set("nodeX","abc.def","bar"); - }).then(function(){ - return context.keys("nodeX").then(function(result){ - result.should.have.length(2); - result[1].should.equal("abc"); + it('should enumerate context keys', function(done) { + context.keys("nodeX",function(err, value){ + value.should.be.an.Array(); + value.should.be.empty(); + context.set("nodeX","foo","bar",function(err){ + context.keys("nodeX",function(err, value){ + value.should.have.length(1); + value[0].should.equal("foo"); + context.set("nodeX","abc.def","bar",function(err){ + context.keys("nodeX",function(err, value){ + value.should.have.length(2); + value[1].should.equal("abc"); + done(); + }); + }); + }); }); }); }); - it('should enumerate context keys in each scopes', function() { - return Promise.all([context.keys("nodeX"), - context.keys("nodeY") - ]).then(function(results){ - results[0].should.be.an.Array(); - results[0].should.be.empty(); - results[1].should.be.an.Array(); - results[1].should.be.empty(); - }).then(function(){ - return Promise.all([context.set("nodeX","foo","bar"), - context.set("nodeY","hoge","piyo")]); - }).then(function(){ - return Promise.all([context.keys("nodeX"), - context.keys("nodeY")]); - }).then(function(results){ - results[0].should.have.length(1); - results[0][0].should.equal("foo"); - results[1].should.have.length(1); - results[1][0].should.equal("hoge"); + it('should enumerate context keys in each scopes', function(done) { + context.keys("nodeX",function(err, value){ + value.should.be.an.Array(); + value.should.be.empty(); + context.keys("nodeY",function(err, value){ + value.should.be.an.Array(); + value.should.be.empty(); + context.set("nodeX","foo","bar",function(err){ + context.set("nodeY","hoge","piyo",function(err){ + context.keys("nodeX",function(err, value){ + value.should.have.length(1); + value[0].should.equal("foo"); + context.keys("nodeY",function(err, value){ + value.should.have.length(1); + value[0].should.equal("hoge"); + done(); + }); + }); + }); + }); + }); }); }); }); describe('#delete',function() { - it('should delete context',function() { - return Promise.all([context.get("nodeX","foo").should.be.finally.undefined(), - context.get("nodeY","foo").should.be.finally.undefined() - ]).then(function(){ - return Promise.all([context.set("nodeX","foo","abc"), - context.set("nodeY","foo","abc")]); - }).then(function(){ - return Promise.all([context.get("nodeX","foo").should.be.finally.equal("abc"), - context.get("nodeY","foo").should.be.finally.equal("abc")]) - }).then(function(){ - return context.delete("nodeX"); - }).then(function(){ - return Promise.all([context.get("nodeX","foo").should.be.finally.undefined(), - context.get("nodeY","foo").should.be.finally.equal("abc")]); + it('should delete context',function(done) { + context.get("nodeX","foo",function(err, value){ + should.not.exist(value); + context.get("nodeY","foo",function(err, value){ + should.not.exist(value); + context.set("nodeX","foo","testX",function(err){ + context.set("nodeY","foo","testY",function(err){ + context.get("nodeX","foo",function(err, value){ + value.should.be.equal("testX"); + context.get("nodeY","foo",function(err, value){ + value.should.be.equal("testY"); + context.delete("nodeX").then(function(){ + context.get("nodeX","foo",function(err, value){ + should.not.exist(value); + context.get("nodeY","foo",function(err, value){ + value.should.be.equal("testY"); + done(); + }); + }); + }); + }); + }); + }); + }); + }); }); }); }); describe('#clean',function() { - it('should clean unnecessary context',function() { - return Promise.all([context.get("nodeX","foo").should.be.finally.undefined(), - context.get("nodeY","foo").should.be.finally.undefined() - ]).then(function(){ - return Promise.all([context.set("nodeX","foo","abc"), - context.set("nodeY","foo","abc")]); - }).then(function(){ - return Promise.all([context.get("nodeX","foo").should.be.finally.equal("abc"), - context.get("nodeY","foo").should.be.finally.equal("abc")]) - }).then(function(){ - return context.clean([]); - }).then(function(){ - return Promise.all([context.get("nodeX","foo").should.be.finally.undefined(), - context.get("nodeY","foo").should.be.finally.undefined()]); + it('should clean unnecessary context',function(done) { + context.get("nodeX","foo",function(err, value){ + should.not.exist(value); + context.get("nodeY","foo",function(err, value){ + should.not.exist(value); + context.set("nodeX","foo","testX",function(err){ + context.set("nodeY","foo","testY",function(err){ + context.get("nodeX","foo",function(err, value){ + value.should.be.equal("testX"); + context.get("nodeY","foo",function(err, value){ + value.should.be.equal("testY"); + context.clean([]).then(function(){ + context.get("nodeX","foo",function(err, value){ + should.not.exist(value); + context.get("nodeY","foo",function(err, value){ + should.not.exist(value); + done(); + }); + }); + }); + }); + }); + }); + }); + }); }); }); - it('should not clean active context',function() { - return Promise.all([context.get("nodeX","foo").should.be.finally.undefined(), - context.get("nodeY","foo").should.be.finally.undefined() - ]).then(function(){ - return Promise.all([context.set("nodeX","foo","abc"), - context.set("nodeY","foo","abc")]); - }).then(function(){ - return Promise.all([context.get("nodeX","foo").should.be.finally.equal("abc"), - context.get("nodeY","foo").should.be.finally.equal("abc")]) - }).then(function(){ - return context.clean(["nodeX"]); - }).then(function(){ - return Promise.all([context.get("nodeX","foo").should.be.finally.equal("abc"), - context.get("nodeY","foo").should.be.finally.undefined()]); + it('should not clean active context',function(done) { + context.get("nodeX","foo",function(err, value){ + should.not.exist(value); + context.get("nodeY","foo",function(err, value){ + should.not.exist(value); + context.set("nodeX","foo","testX",function(err){ + context.set("nodeY","foo","testY",function(err){ + context.get("nodeX","foo",function(err, value){ + value.should.be.equal("testX"); + context.get("nodeY","foo",function(err, value){ + value.should.be.equal("testY"); + context.clean(["nodeX"]).then(function(){ + context.get("nodeX","foo",function(err, value){ + value.should.be.equal("testX"); + context.get("nodeY","foo",function(err, value){ + should.not.exist(value); + done(); + }); + }); + }); + }); + }); + }); + }); + }); }); }); }); From cce7ac09d0fc959b1829f764c63ab0ac18a74988 Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Tue, 26 Jun 2018 11:36:37 +0900 Subject: [PATCH 31/32] Add callback handling to memory plugin --- red/runtime/nodes/context/memory.js | 65 ++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/red/runtime/nodes/context/memory.js b/red/runtime/nodes/context/memory.js index 3010b4315..b6f687f96 100644 --- a/red/runtime/nodes/context/memory.js +++ b/red/runtime/nodes/context/memory.js @@ -28,30 +28,67 @@ Memory.prototype.close = function(){ return Promise.resolve(); }; -Memory.prototype.get = function(scope, key) { - if(!this.data[scope]){ - return undefined; +Memory.prototype.get = function(scope, key, callback) { + var value; + try{ + if(this.data[scope]){ + value = util.getMessageProperty(this.data[scope], key); + } + }catch(err){ + if(callback){ + callback(err); + }else{ + throw err; + } + } + if(callback){ + callback(null, value); + } else { + return value; } - return util.getMessageProperty(this.data[scope],key); }; -Memory.prototype.set =function(scope, key, value) { +Memory.prototype.set =function(scope, key, value, callback) { if(!this.data[scope]){ this.data[scope] = {}; } - util.setMessageProperty(this.data[scope],key,value); + try{ + util.setMessageProperty(this.data[scope],key,value); + }catch(err){ + if(callback){ + callback(err); + }else{ + throw err; + } + } + if(callback){ + callback(null); + } }; -Memory.prototype.keys = function(scope){ - if(!this.data[scope]){ - return []; +Memory.prototype.keys = function(scope, callback){ + var values = []; + try{ + if(this.data[scope]){ + if (scope !== "global") { + values = Object.keys(this.data[scope]); + } else { + values = Object.keys(this.data[scope]).filter(function (key) { + return key !== "set" && key !== "get" && key !== "keys"; + }); + } + } + }catch(err){ + if(callback){ + callback(err); + }else{ + throw err; + } } - if (scope !== "global") { - return Object.keys(this.data[scope]); + if(callback){ + callback(null, values); } else { - return Object.keys(this.data[scope]).filter(function (key) { - return key !== "set" && key !== "get" && key !== "keys"; - }); + return values; } }; From 40ff54f67e5209e6ea23e440b14cd069995f6b56 Mon Sep 17 00:00:00 2001 From: HirokiUchikawa Date: Tue, 26 Jun 2018 11:43:37 +0900 Subject: [PATCH 32/32] Improve context storage handling --- red/runtime/nodes/context/index.js | 61 +++++++++++++------- test/red/runtime/nodes/context/index_spec.js | 2 +- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index ac52ca152..8ea3f5823 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -22,7 +22,7 @@ var settings; var contexts = {}; var globalContext = null; var externalContexts = {}; -var noContextStorage = false; +var noContextStorage = true; function init(_settings) { settings = _settings; @@ -96,9 +96,7 @@ function copySettings(config, settings){ } function getContextStorage(storage) { - if (noContextStorage || !storage) { - return externalContexts["_"]; - } else if (externalContexts.hasOwnProperty(storage)) { + if (externalContexts.hasOwnProperty(storage)) { return externalContexts[storage]; } else if (externalContexts.hasOwnProperty("default")) { return externalContexts["default"]; @@ -114,29 +112,52 @@ function createContext(id,seed) { var obj = seed || {}; obj.get = function(key, storage, callback) { - if (typeof storage === 'function') { - callback = storage; - storage = "default"; - } else if(typeof storage === "string" && typeof callback !== 'function'){ - throw new Error("Callback must be a function"); + var context; + if (!storage && !callback) { + context = externalContexts["_"]; + } else { + if (typeof storage === 'function') { + callback = storage; + storage = "default"; + } + if (typeof callback !== 'function'){ + throw new Error("Callback must be a function"); + } + context = getContextStorage(storage); } - return getContextStorage(storage).get(scope, key, callback); + return context.get(scope, key, callback); }; obj.set = function(key, value, storage, callback) { - if (typeof storage === 'function') { - callback = storage; - storage = "default"; + var context; + if (!storage && !callback) { + context = externalContexts["_"]; + } else { + if (typeof storage === 'function') { + callback = storage; + storage = "default"; + } + if (callback && typeof callback !== 'function') { + throw new Error("Callback must be a function"); + } + context = getContextStorage(storage); } - getContextStorage(storage).set(scope, key, value, callback); + context.set(scope, key, value, callback); }; obj.keys = function(storage, callback) { - if (typeof storage === 'function') { - callback = storage; - storage = "default"; - } else if(typeof storage === "string" && typeof callback !== 'function'){ - throw new Error("Callback must be a function"); + var context; + if (!storage && !callback) { + context = externalContexts["_"]; + } else { + if (typeof storage === 'function') { + callback = storage; + storage = "default"; + } + if (typeof callback !== 'function') { + throw new Error("Callback must be a function"); + } + context = getContextStorage(storage); } - return getContextStorage(storage).keys(scope, callback); + return context.keys(scope, callback); }; return obj; } diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index bc597d0b9..fa1332f20 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -263,7 +263,7 @@ describe('context', function() { Context.init({contextStorage:{_:{module:testPlugin}}}); Context.load().then(function(){ var context = Context.get("1","flow"); - var cb = function(){done("An error occurred")} + var cb = function(){} context.set("foo","bar","_",cb); context.get("foo","_",cb); context.keys("_",cb);