From 20c8c7426fefacf685494302f908c0919c779a9c Mon Sep 17 00:00:00 2001 From: Rahix Date: Thu, 13 Apr 2017 21:29:47 +0200 Subject: [PATCH] Make oc-basecontrol more robust - Allow init without params - Remove errors, instead indicate failures with return values - Add has_noun and has_verb - Allow late init of listeners --- bc.lua | 44 +++++++++++++++++++++++------ modem.lua | 8 +++++- test_bc.lua | 81 +++++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 109 insertions(+), 24 deletions(-) diff --git a/bc.lua b/bc.lua index 68dc324d2dc5..05b313c7d8c1 100644 --- a/bc.lua +++ b/bc.lua @@ -31,7 +31,7 @@ function List:iter() i = i + 1 if i <= last then if list[i] ~= nil then - return list[i] + return i, list[i] end else return nil @@ -63,6 +63,8 @@ CFG_PORT = 1234 bc = {} function bc:init(my_address, local_nouns, local_verbs) + local_nouns = local_nouns or {} + local_verbs = local_verbs or {} local o = {} setmetatable(o, self) self.__index = self @@ -93,6 +95,18 @@ function bc:init(my_address, local_nouns, local_verbs) end for i,noun in ipairs(message.nouns) do o.remote_nouns[noun] = remoteAddress + -- Check for potential dormant listeners + if o.remote_listeners[noun] ~= nil then + for id, listener in o.remote_listeners[noun]:iter() do + o.modem:send(remoteAddress, port, serializer.serialize({ + ty=message_type.request_listening, + noun=noun, + query=listener.query, + qparam=listener.qparam, + id=id, + })) + end + end end -- Respond with own verbs and nouns local mynouns = {} @@ -145,22 +159,27 @@ function bc:get_noun(n) return self.local_nouns[n] elseif self.remote_nouns[n] ~= nil then -- This noun is remote self.modem:send(self.remote_nouns[n], CFG_PORT, serializer.serialize({ty=message_type.request_noun, noun=n})) - while self._nr == nil do - -- Nothing, wait for response, TODO: Research performance impact + if self._nr == nil then + return nil end local value = self._nr self._nr = nil -- Reset return value else - error("Noun not found, Node might be offline") + -- Noun not found + return nil end end +function bc:has_noun(n) + return self.local_nouns[n] ~= nil or self.remote_nouns[n] ~= nil +end + function bc:set_noun(n, value) if self.local_nouns[n] ~= nil then -- Call local listeners if self.local_listeners[n] ~= nil then - for listener in self.local_listeners[n]:iter() do + for id, listener in self.local_listeners[n]:iter() do if (listener.query == "onchange" and self.local_nouns[n] ~= value) or (listener.query == "onrising" and self.local_nouns[n] < value) or (listener.query == "onfalling" and self.local_nouns[n] > value) @@ -202,13 +221,19 @@ end function bc:call_verb(v, param) if self.local_verbs[v] ~= nil then self.local_verbs[v](self, param) + return true elseif self.remote_verbs[v] ~= nil then self.modem:send(self.remote_verbs[v], CFG_PORT, serializer.serialize({ty=message_type.call_verb, verb=v, param=param})) + return true else - error("Verb not found, Node might be offline") + return false end end +function bc:has_verb(v) + return self.local_verbs[v] ~= nil or self.remote_verbs[v] ~= nil +end + function bc:listen_noun(n, query, qparam, callback) if self.local_nouns[n] ~= nil then -- Local listening self.local_listeners[n] = self.local_listeners[n] or List:new() @@ -221,7 +246,10 @@ function bc:listen_noun(n, query, qparam, callback) self.modem:send(self.remote_nouns[n], CFG_PORT, serializer.serialize({ty=message_type.request_listening, noun=n, query=query, qparam=qparam, id=id})) return id else - error("Noun not found, Node might be offline") + -- Install it, with hope, that the node will come online later + self.remote_listeners[n] = self.remote_listeners[n] or List:new() + local id = self.remote_listeners[n]:insert({query=query, qparam=qparam, callback=callback}) + return id end end @@ -236,7 +264,7 @@ function bc:listen_cancel(n, id) self.modem:send(self.remote_nouns[n], CFG_PORT, serializer.serialize({ty=message_type.request_stop_listening, noun=n, id=id})) end else - error("Noun not found, Node might be offline") + error("Trying to cancel non existing listener") end end diff --git a/modem.lua b/modem.lua index 0a2073c2bcc6..b83b477ab039 100644 --- a/modem.lua +++ b/modem.lua @@ -17,7 +17,9 @@ function modem:open(port) end function modem:send(address, port, msg) - self.network[address](address, self.address, port, 0, msg) + if self.network[address] ~= nil then + self.network[address](address, self.address, port, 0, msg) + end end function modem:broadcast(port, msg) @@ -28,4 +30,8 @@ function modem:broadcast(port, msg) end end +function modem:remove_self() + self.network[self.address] = nil +end + return modem diff --git a/test_bc.lua b/test_bc.lua index 824df747f38a..61d661d4081c 100644 --- a/test_bc.lua +++ b/test_bc.lua @@ -6,18 +6,20 @@ ser = require("serialization") module("test_bc", package.seeall, lunit.testcase) function test_init() - bci = bc:init("a1", {["light"]=false}, {["toggle_light"]=function() end}) + local bci = bc:init("a1", {["light"]=false}, {["toggle_light"]=function() end}) + assert_true(bci:has_noun("light"), "Nouns were not initialized") + assert_true(bci:has_verb("toggle_light"), "Verbs were not initialized") end function test_set_get_noun() - bci = bc:init("a1", {["light"]=true}, {["toggle_light"]=function() end}) + local bci = bc:init("a1", {["light"]=true}, {["toggle_light"]=function() end}) assert_equal(true, bci:get_noun("light"), "First get failed with wrong value") bci:set_noun("light", false) assert_equal(false, bci:get_noun("light"), "Second get failed with wrong value") end function test_request_noun() - bci = bc:init("a1", {["light"]=true}, {["toggle_light"]=function() end}) + local bci = bc:init("a1", {["light"]=true}, {["toggle_light"]=function() end}) local i = false local expct = true remote = require("modem"):init("some_addr", function(laddr, raddr, p, d, msg) @@ -37,7 +39,7 @@ function test_request_noun() end function test_call_verb() - bci = bc:init("a1", {["light"]=true}, {["toggle_light"]=function(b) + local bci = bc:init("a1", {["light"]=true}, {["toggle_light"]=function(b) b:set_noun("light", not b:get_noun("light")) end}) remote = require("modem"):init("some_addr", function(laddr, raddr, p, d, msg) end) @@ -47,29 +49,29 @@ function test_call_verb() end function test_multinode_call_verb() - bc1 = bc:init("a1", {["light"]=true}, {["toggle_light"]=function(b) + local bc1 = bc:init("a1", {["light"]=true}, {["toggle_light"]=function(b) b:set_noun("light", not b:get_noun("light")) end}) - bc2 = bc:init("a2", {}, {}) - bc1:call_verb("toggle_light") + local bc2 = bc:init("a2", {}, {}) + assert_true(bc1:call_verb("toggle_light")) assert_equal(false, bc1:get_noun("light"), "First verb invokation did not go to plan") - bc2:call_verb("toggle_light") + assert_true(bc2:call_verb("toggle_light")) assert_equal(true, bc1:get_noun("light"), "Second verb invokation did not go to plan") end function test_multinode_get_noun() - key = "foobar" - bc1 = bc:init("a1", {["light"]=key}, {["toggle_light"]=function(b) + local key = "foobar" + local bc1 = bc:init("a1", {["light"]=key}, {["toggle_light"]=function(b) b:set_noun("light", not b:get_noun("light")) end}) - bc2 = bc:init("a2", {}, {}) + local bc2 = bc:init("a2", {}, {}) assert_equal(key, bc1:get_noun("light"), "Local get failed") assert_equal(key, bc2:get_noun("light"), "Remote get failed") end function test_multinode_listening() - bc1 = bc:init("a1", {["foo"]=123}, {}) - bc2 = bc:init("a2", {}, {}) + local bc1 = bc:init("a1", {["foo"]=123}, {}) + local bc2 = bc:init("a2", {}, {}) local i = false -- Local listening local id = bc1:listen_noun("foo", "onchange", nil, function(bc, foo) @@ -93,11 +95,11 @@ function test_multinode_listening() end function test_listen_modes() - bci = bc:init("a1", {["noun"]=10}, {}) + local bci = bc:init("a1", {["noun"]=10}, {}) local i = false local id = 0 -- onchange - id = bci:listen_noun("noun", "onchange", nil, function(bc, noun) + local id = bci:listen_noun("noun", "onchange", nil, function(bc, noun) i = true end) bci:set_noun("noun", 11) @@ -154,3 +156,52 @@ function test_listen_modes() assert_true(i, "Listening \"onbelow\" failed(B)") bci:listen_cancel("noun", id) end + +function test_get_unknown_noun() + local bci = bc:init("a1") + + assert_false(bci:has_noun("bar")) + assert_equal(nil, bci:get_noun("bar")) +end + +function test_get_offline_node() + local bc1 = bc:init("a1") + local bc2 = bc:init("a2", {["foo"]=true}) + + -- Simulate node going offline + bc2.modem:remove_self() + bc2 = nil + + local val = bc1:get_noun("foo") + assert_equal(nil, val) +end + +function test_call_unknown_verb() + local bci = bc:init("a1") + + assert_false(bci:has_verb("unknown")) + assert_equal(false, bci:call_verb("unknown")) +end + +function test_install_listen_on_unknown_noun() + local bci = bc:init("a1") + + assert_false(bci:has_noun("unknown")) + bci:listen_noun("unknown", "onchange", nil, function() end) +end + +function test_late_install() + local bc1 = bc:init("a1") + + local i = false + + assert_false(bc1:has_noun("foo")) + bc1:listen_noun("foo", "onchange", nil, function() + i = true + end) + + local bc2 = bc:init("a2", {["foo"] = 123}) + bc2:set_noun("foo", 321) + + assert_true(i, "Listener was not called") +end