diff --git a/kam_utils.lua b/kam_utils.lua index 4c229cd..04875ac 100644 --- a/kam_utils.lua +++ b/kam_utils.lua @@ -1,5 +1,33 @@ #!/usr/bin/env lua5.1 -# Kamailio Lua utils +-- Kamailio Lua utils + +-- kamailio log for a table +function table.log(t, msg, level) + if not level then + level = "debug" + end + if msg then + sr.log(level, msg) + end + if not t then + -- empty table + return + end + for i,v in pairs(t) do + if type(i) == "number" then + iformat = "%d" + elseif type(i) == "string" then + iformat = "%s" + end + if type(v) == "string" then + sr.log(level, string.format("i:" .. iformat .. " v: %s", i, v)) + elseif type(v) == "number" then + sr.log(level, string.format("i:" .. iformat .. " v: %d", i, v)) + elseif type(v) == "table" then + table.log(v,string.format("i:" .. iformat .. " v:", i),level) + end + end +end -- cleans and sets string values from the table list function sets_avps(list) @@ -24,6 +52,9 @@ function seti_avps(list) end function clean_avps(list) + if not list then + error("list is empty") + end local i,v for i,v in pairs(list) do @@ -31,4 +62,4 @@ function clean_avps(list) end end -#EOF \ No newline at end of file +--EOF \ No newline at end of file diff --git a/lemock.lua b/lemock.lua new file mode 100644 index 0000000..a25bc8c --- /dev/null +++ b/lemock.lua @@ -0,0 +1,659 @@ +------ THIS FILE IS TANGLED FROM LITERATE SOURCE FILES ------ +-- Copyright (C) 2009 Tommy Pettersson +-- See terms in file COPYRIGHT, or at http://lemock.luaforge.net +module( 'lemock', package.seeall ) +_VERSION = "LeMock 0.6" +_COPYRIGHT = "Copyright (C) 2009 Tommy Pettersson " +local class, object, qtostring, sfmt, add_to_set +local elements_of_set, value_equal +function object (class) + return setmetatable( {}, class ) +end +function class (parent) + local c = object(parent) + c.__index = c + return c +end +sfmt = string.format +function qtostring (v) + if type(v) == 'string' then + return sfmt( '%q', v ) + else + return tostring( v ) + end +end +function add_to_set (o, setname, element) + if not o[setname] then + o[setname] = {} + end + local l = o[setname] + for i = 1, #l do + if l[i] == element then return end + end + l[#l+1] = element +end +function elements_of_set (o, setname) + local l = o[setname] + local i = l and #l+1 or 0 + return function () + i = i - 1 + if i > 0 then return l[i] end + end +end +function value_equal (a, b) + if a == b then return true end + if a ~= a and b ~= b then return true end -- NaN == NaN + return false +end +local mock_controller_map = setmetatable( {}, {__mode='k'} ) +-- All the classes are private +local Action, Argv, Callable, Controller, Mock +Action = {} +-- abstract +Action.generic = class() +function Action.generic:add_close (label) + add_to_set( self, 'closelist', label ) +end +function Action.generic:add_depend (d) + add_to_set( self, 'dependlist', d ) +end +function Action.generic:add_label (label) + add_to_set( self, 'labellist', label ) +end +function Action.generic:assert_satisfied () + assert( self.replay_count <= self.max_replays, "lemock internal error" ) + if not ( +self.min_replays <= self.replay_count + ) then + error( sfmt( "Wrong replay count %d (expected %d..%d) for %s" + , self.replay_count + , self.min_replays, self.max_replays + , self:tostring() + ) + , 0 + ) + end +end +function Action.generic:blocks () + if self:is_satisfied() then + return function () end + end + return elements_of_set( self, 'labellist' ) +end +function Action.generic:closes () + return elements_of_set( self, 'closelist' ) +end +function Action.generic:depends () + return elements_of_set( self, 'dependlist' ) +end +function Action.generic:has_label (l) + for x in elements_of_set( self, 'labellist' ) do + if x == l then return true end + end + return false +end +function Action.generic:is_expected () + return self.replay_count < self.max_replays + and not self.is_blocked + and not self.is_closed +end +function Action.generic:is_satisfied () + return +self.min_replays <= self.replay_count +end +function Action.generic:match (key) + if getmetatable(self) ~= getmetatable(key) then return false end + if self.mock ~= key.mock then return false end + return self:is_expected() +end +function Action.generic:new (mock) + local a = object( self ) + a.mock = mock + a.replay_count = 0 + a.min_replays = 1 + a.max_replays = 1 + return a +end +function Action.generic:set_times (a, b) + min = a or 1 + max = b or min + min, max = tonumber(min), tonumber(max) + if (not min) or (not max) or (min >= math.huge) + or (min ~= min) or (max ~= max) -- NaN + or (min < 0) or (max <= 0) or (min > max) then + error( sfmt( "Unrealistic time arguments (%s, %s)" + , qtostring( min ) + , qtostring( max ) + ) + , 0 + ) + end + self.min_replays = min + self.max_replays = max +end +Action.generic_call = class( Action.generic ) +Action.generic_call.can_return = true +function Action.generic_call:get_returnvalue () + if self.has_returnvalue then + return self.returnvalue:unpack() + end +end +function Action.generic_call:set_returnvalue (...) + self.returnvalue = Argv:new(...) + self.has_returnvalue = true +end +function Action.generic_call:match (q) + if not Action.generic.match( self, q ) then return false end + if not self.argv:equal( q.argv ) then return false end + return true +end +function Action.generic_call:new (m, ...) + local a = Action.generic.new( self, m ) + a.argv = Argv:new(...) + return a +end +-- concrete +Action.call = class( Action.generic_call ) +function Action.call:match (q) + if not Action.generic_call.match( self, q ) then return false end + if self.key ~= q.key then return false end + return true +end +function Action.call:new (m, key, ...) + local a = Action.generic_call.new( self, m, ... ) + a.key = key + return a +end +function Action.call:tostring () + if self.has_returnvalue then + return sfmt( "call %s(%s) => %s" + , tostring(self.key) + , self.argv:tostring() + , self.returnvalue:tostring() + ) + else + return sfmt( "call %s(%s)" + , tostring(self.key) + , self.argv:tostring() + ) + end +end +Action.index = class( Action.generic ) +Action.index.can_return = true +function Action.index:get_returnvalue () + return self.returnvalue +end +function Action.index:set_returnvalue (v) + self.returnvalue = v + self.has_returnvalue = true +end +function Action.index:match (q) + if not Action.generic.match( self, q ) then return false end + if self.key ~= q.key then return false end + return true +end +function Action.index:new (m, key) + local a = Action.generic.new( self, m ) + a.key = key + return a +end +function Action.index:tostring () + local key = 'index '..tostring( self.key ) + if self.has_returnvalue then + return sfmt( "index %s => %s" + , tostring( self.key ) + , qtostring( self.returnvalue ) + ) + elseif self.is_callable then + return sfmt( "index %s()" + , tostring( self.key ) + ) + else + return sfmt( "index %s" + , tostring( self.key ) + ) + end +end +Action.newindex = class( Action.generic ) +function Action.newindex:match (q) + if not Action.generic.match( self, q ) then return false end + if self.key ~= q.key then return false end + if not value_equal( self.val, q.val ) + and self.val ~= Argv.ANYARG + and q.val ~= Argv.ANYARG then return false end + return true +end +function Action.newindex:new (m, key, val) + local a = Action.generic.new( self, m ) + a.key = key + a.val = val + return a +end +function Action.newindex:tostring () + return sfmt( "newindex %s = %s" + , tostring(self.key) + , qtostring(self.val) + ) +end +Action.selfcall = class( Action.generic_call ) +function Action.selfcall:match (q) + return Action.generic_call.match( self, q ) +end +function Action.selfcall:new (m, ...) + local a = Action.generic_call.new( self, m, ... ) + return a +end +function Action.selfcall:tostring () + if self.has_returnvalue then + return sfmt( "selfcall (%s) => %s" + , self.argv:tostring() + , self.returnvalue:tostring() + ) + else + return sfmt( "selfcall (%s)" + , self.argv:tostring() + ) + end +end +Argv = class() +Argv.ANYARGS = newproxy() local ANYARGS = Argv.ANYARGS +Argv.ANYARG = newproxy() local ANYARG = Argv.ANYARG +function Argv:equal (other) + local a1, n1 = self.v, self.len + local a2, n2 = other.v, other.len + if n1-1 <= n2 and a1[n1] == ANYARGS then + n1 = n1-1 + n2 = n1 + elseif n2-1 <= n1 and a2[n2] == ANYARGS then + n2 = n2-1 + n1 = n2 + end + if n1 ~= n2 then + return false + end + for i = 1, n1 do + local v1, v2 = a1[i], a2[i] + if not value_equal(v1,v2) and v1 ~= ANYARG and v2 ~= ANYARG then + return false + end + end + return true +end +function Argv:new (...) + local av = object( self ) + av.v = {...} + av.len = select('#',...) + for i = 1, av.len - 1 do + if av.v[i] == Argv.ANYARGS then + error( "ANYARGS not at end.", 0 ) + end + end + return av +end +function Argv:tostring () + local res = {} + local function w (v) + res[#res+1] = qtostring( v ) + end + local av, ac = self.v, self.len + for i = 1, ac do + if av[i] == Argv.ANYARG then + res[#res+1] = 'ANYARG' + elseif av[i] == Argv.ANYARGS then + res[#res+1] = 'ANYARGS' + else + w( av[i] ) + end + if i < ac then + res[#res+1] = ',' -- can not use qtostring in w() + end + end + return table.concat( res ) +end +function Argv:unpack () + return unpack( self.v, 1, self.len ) +end +Callable = {} +Callable.generic = class() +Callable.record = class( Callable.generic ) +Callable.replay = class( Callable.generic ) +function Callable.generic:new ( index_action ) + local f = object( self ) + f.action = index_action + return f +end +function Callable.record:__call (...) + local index_action = self.action + local m = index_action.mock + local mc = mock_controller_map[m] + assert( mc.is_recording, "client uses cached callable from recording" ) + mc:make_callable( index_action ) + mc:add_action( Action.call:new( m, index_action.key, ... )) +end +function Callable.replay:__call (...) + local index_action = self.action + local m = index_action.mock + local mc = mock_controller_map[m] + local call_action = mc:lookup( Action.call:new( m, index_action.key, ... )) + mc:replay_action( call_action ) + if call_action.throws_error then + error( call_action.errorvalue, 2 ) + end + return call_action:get_returnvalue() +end +Controller = class() +-- Exported methods +function Controller:close (...) + if not self.is_recording then + error( "Can not insert close in replay mode.", 2 ) + end + local action = self:get_last_action() + for _, close in ipairs{ ... } do + action:add_close( close ) + end + return self -- for chaining +end +function Controller:depend (...) + if not self.is_recording then + error( "Can not add dependency in replay mode.", 2 ) + end + local action = self:get_last_action() + for _, dependency in ipairs{ ... } do + action:add_depend( dependency ) + end + return self -- for chaining +end +function Controller:error (value) + if not self.is_recording then + error( "Error called during replay.", 2 ) + end + local action = self:get_last_action() + if action.has_returnvalue or action.throws_error then + error( "Returns and/or Error called twice for same action.", 2 ) + end + action.throws_error = true + action.errorvalue = value + return self -- for chaining +end +function Controller:label (...) +if not self.is_recording then + error( "Can not add labels in replay mode.", 2 ) +end +local action = self:get_last_action() +for _, label in ipairs{ ... } do + action:add_label( label ) +end +return self -- for chaining +end +function Controller:mock () + if not self.is_recording then + error( "New mock during replay.", 2 ) + end + local m = object( Mock.record ) + mock_controller_map[m] = self + return m +end +function Controller:new () + local mc = object( self ) + mc.actionlist = {} + mc.is_recording = true + return mc +end +function Controller:replay () + if not self.is_recording then + error( "Replay called twice.", 2 ) + end + self.is_recording = false + for m, mc in pairs( mock_controller_map ) do + if mc == self then + setmetatable( m, Mock.replay ) + end + end + self:update_dependencies() + self:assert_no_dependency_cycles() +end +function Controller:returns (...) + if not self.is_recording then + error( "Returns called during replay.", 2 ) + end + local action = self:get_last_action() + assert( not action.is_callable, "lemock internal error" ) + if not action.can_return then + error( "Previous action can not return anything.", 2 ) + end + if action.has_returnvalue or action.throws_error then + error( "Returns and/or Error called twice for same action.", 2 ) + end + action:set_returnvalue(...) + return self -- for chaining +end +function Controller:times (min, max) + if not self.is_recording then + error( "Can not set times in replay mode.", 0 ) + end + self:get_last_action():set_times( min, max ) + return self -- for chaining +end +-- convenience functions +function Controller:anytimes() return self:times( 0, math.huge ) end +function Controller:atleastonce() return self:times( 1, math.huge ) end +function Controller:verify () + if self.is_recording then + error( "Verify called during record.", 2 ) + end + for a in self:actions() do + a:assert_satisfied() + end +end +-- Protected methods +function Controller:actions (q) + local l = self.actionlist + local i = 0 + return function () + i = i + 1 + return l[i] + end +end +function Controller:add_action (a) + assert( a ~= nil, "lemock internal error" ) -- breaks array property + table.insert( self.actionlist, a ) +end +function Controller:assert_no_dependency_cycles () + local function is_in_path (label, path) + if not path then return false end -- is root + for _, l in ipairs( path ) do + if l == label then return true end + end + if path.prev then return is_in_path( label, path.prev ) end + return false + end + local function can_block (action, node) + for _, label in ipairs( node ) do + if action:has_label( label ) then return true end + end + return false + end + local function step (action, path) + local new_head + for label in action:depends() do + if is_in_path( label, path ) then + error( "Detected dependency cycle", 0 ) + end + -- only create table if needed to reduce garbage + if not new_head then new_head = { prev=path } end + new_head[#new_head+1] = label + end + return new_head + end + local function search_depth_first (path) + for action in self:actions() do + if can_block( action, path ) then + local new_head = step( action, path ) + if new_head then + search_depth_first( new_head ) + end + end + end + end + for action in self:actions() do + local root = step( action, nil ) + if root then search_depth_first( root ) end + end +end +function Controller:close_actions( ... ) -- takes iterator + for label in ... do + for candidate in self:actions() do + if candidate:has_label( label ) then + if not candidate:is_satisfied() then + error( "Closes unsatisfied action: "..candidate:tostring(), 0 ) + end + candidate.is_closed = true + end + end + end +end +function Controller:get_last_action () + local l = self.actionlist + if #l == 0 then + error( "No action is recorded yet.", 0 ) + end + return l[#l] +end +function Controller:lookup (actual) + for action in self:actions() do + if action:match( actual ) then + return action + end + end +local expected = {} +for _, a in ipairs( self.actionlist ) do + if a:is_expected() and not a.is_callable then + expected[#expected+1] = a:tostring() + end +end +table.sort( expected ) +if #expected == 0 then + expected[1] = "(Nothing)" +end + error( sfmt( "Unexpected action %s, expected:\n%s\n" + , actual:tostring() + , table.concat(expected,'\n') + ) + , 0 + ) +end +function Controller:make_callable (action) + if action.has_returnvalue then + error( "Can not call "..action.key..". It has a returnvalue.", 0 ) + end + action.is_callable = true + action.min_replays = 0 + action.max_replays = math.huge +end +function Controller:new () + local mc = object( self ) + mc.actionlist = {} + mc.is_recording = true + return mc +end +function Controller:replay_action ( action ) + assert( action:is_expected(), "lemock internal error" ) + assert( action.replay_count < action.max_replays, "lemock internal error" ) + local was_satisfied = action:is_satisfied() + action.replay_count = action.replay_count + 1 + if not was_satisfied and action.labellist and action:is_satisfied() then + self:update_dependencies() + end + if action.closelist then + self:close_actions( action:closes() ) + end +end +function Controller:update_dependencies () + local blocked = {} + for action in self:actions() do + for label in action:blocks() do + blocked[label] = true + end + end + local function is_blocked (action) + for label in action:depends() do + if blocked[label] then return true end + end + return false + end + for action in self:actions() do + action.is_blocked = is_blocked( action ) + end +end +Mock = { record={}, replay={} } -- no self-referencing __index! +function Mock.record:__index (key) + local mc = mock_controller_map[self] + local action = Action.index:new( self, key ) + mc:add_action( action ) + return Callable.record:new( action ) +end +function Mock.record:__newindex (key, val) + local mc = mock_controller_map[self] + mc:add_action( Action.newindex:new( self, key, val )) +end +function Mock.record:__call (...) + local mc = mock_controller_map[self] + mc:add_action( Action.selfcall:new( self, ... )) +end +function Mock.replay:__index (key) + local mc = mock_controller_map[self] + local index_action = mc:lookup( Action.index:new( self, key )) + mc:replay_action( index_action ) + if index_action.throws_error then + error( index_action.errorvalue, 2 ) + end + if index_action.is_callable then + return Callable.replay:new( index_action ) + else + return index_action:get_returnvalue() + end +end +function Mock.replay:__newindex (key, val) + local mc = mock_controller_map[self] + local newindex_action = mc:lookup( Action.newindex:new( self, key, val )) + mc:replay_action( newindex_action ) + if newindex_action.throws_error then + error( newindex_action.errorvalue, 2 ) + end +end +function Mock.replay:__call (...) + local mc = mock_controller_map[self] + local selfcall_action = mc:lookup( Action.selfcall:new( self, ... )) + mc:replay_action( selfcall_action ) + if selfcall_action.throws_error then + error( selfcall_action.errorvalue, 2 ) + end + return selfcall_action:get_returnvalue() +end +function controller () + local exported_methods = { + 'anytimes', + 'atleastonce', + 'close', + 'depend', + 'error', + 'label', + 'mock', + 'new', + 'replay', + 'returns', + 'times', + 'verify', + } + local mc = Controller:new() + local wrapper = {} + for _, method in ipairs( exported_methods ) do + wrapper[ method ] = function (self, ...) + return mc[ method ]( mc, ... ) + end + end + wrapper.ANYARG = Argv.ANYARG + wrapper.ANYARGS = Argv.ANYARGS + return wrapper +end +return _M diff --git a/mocks/sr.lua b/mocks/sr.lua new file mode 100644 index 0000000..1766b59 --- /dev/null +++ b/mocks/sr.lua @@ -0,0 +1,17 @@ +#!/usr/bin/env lua5.1 +require 'lemock' + +mc = lemock.controller() + +srMock = { + __class__ = 'srMock', + pv = mc:mock() +} +srMock_MT = { __index = srMock, __newindex = mc:mock() } + function srMock:new() + --print("srMock:new") + local t = {} + setmetatable(t, srMock_MT) + return t + end +--EOF \ No newline at end of file diff --git a/ngcp/dp.lua b/ngcp/dp.lua new file mode 100644 index 0000000..5c516fe --- /dev/null +++ b/ngcp/dp.lua @@ -0,0 +1,28 @@ +#!/usr/bin/env lua5.1 +require 'ngcp.pref' + +-- class NGCPDomainPrefs +NGCPDomainPrefs = { + __class__ = 'NGCPDomainPrefs' +} +NGCPDomainPrefs_MT = { __index = NGCPDomainPrefs, __newindex = NGCPPrefs } + + function NGCPDomainPrefs:new() + local t = NGCPDomainPrefs.init() + setmetatable( t, NGCPDomainPrefs_MT ) + return t + end + + function NGCPDomainPrefs.init() + local t = NGCPPrefs.init() + return t + end + + function NGCPDomainPrefs:clean(...) + --print("NGCPDomainPrefs:clean") + --print(table.tostring(getmetatable(self))) + --print(table.tostring(self)) + NGCPPrefs.clean(self, ...) + end +-- class +--EOF \ No newline at end of file diff --git a/ngcp.lua b/ngcp/ngcp.lua similarity index 50% rename from ngcp.lua rename to ngcp/ngcp.lua index 502ca92..c71323e 100644 --- a/ngcp.lua +++ b/ngcp/ngcp.lua @@ -1,18 +1,6 @@ #!/usr/bin/env lua5.1 - --- class NGCPPreference -NGCPPreference = { - __class__ = 'NGCPPreference' -} -NGCPPreference_MT = { __index = NGCPPreference } - - function NGCPPreference:new(name) - local t = {} - t.name = name - setmetatable( t, NGCPPreference_MT ) - return t - end --- class +require 'ngcp.pp' +require 'ngcp.dp' -- class NGCPConfig NGCPConfig = { @@ -34,14 +22,20 @@ NGCP = { NGCP_MT = { __index = NGCP } function NGCP:new() - local t = {} - t.config = NGCPConfig:new() - t.preference = { - domain = NGCPPreference:new('domain'), - peer = NGCPPreference:new('peer'), - } + local t = NGCP.init() setmetatable( t, NGCP_MT ) return t end + + function NGCP.init() + local t = { + config = NGCPConfig:new(), + prefs = { + domain = NGCPDomainPrefs:new(), + peer = NGCPPeerPrefs:new() + } + } + return t + end -- class --EOF \ No newline at end of file diff --git a/ngcp/pp.lua b/ngcp/pp.lua new file mode 100644 index 0000000..d14a435 --- /dev/null +++ b/ngcp/pp.lua @@ -0,0 +1,88 @@ +#!/usr/bin/env lua5.1 +require 'ngcp.pref' + +-- class NGCPPeerPrefs +NGCPPeerPrefs = { + __class__ = 'NGCPPeerPrefs' +} +NGCPPeerPrefs_MT = { __index = NGCPPeerPrefs, __newindex = NGCPPrefs } + + function NGCPPeerPrefs:new() + local t = NGCPPeerPrefs.init() + setmetatable( t, NGCPPeerPrefs_MT ) + return t + end + + function NGCPPeerPrefs.init() + local t = NGCPPrefs.init() + t.inbound = { + peer_peer_callee_auth_user = "", + peer_peer_callee_auth_pass = "", + peer_peer_callee_auth_realm = "", + caller_use_rtpproxy = "", + peer_caller_ipv46_for_rtpproxy = "", + caller_force_outbound_calls_to_peer = "", + peer_caller_find_subscriber_by_uuid = "", + pstn_dp_caller_in_id = "", + pstn_dp_callee_in_id = "", + pstn_dp_caller_out_id = "", + pstn_dp_callee_out_id = "", + rewrite_caller_in_dpid = "", + rewrite_caller_out_dpid = "", + rewrite_callee_in_dpid = "", + rewrite_callee_out_dpid = "", + caller_peer_concurrent_max = "", + peer_caller_sst_enable = "", + peer_caller_sst_expires = "", + peer_caller_sst_min_timer = "", + peer_caller_sst_max_timer = "", + peer_caller_sst_refresh_method = "", + caller_inbound_upn = "", + caller_inbound_npn = "", + caller_inbound_uprn = "" + } + t.outbound = { + peer_peer_caller_auth_user = "", + peer_peer_caller_auth_pass = "", + peer_peer_caller_auth_realm = "", + callee_use_rtpproxy = "", + peer_callee_ipv46_for_rtpproxy = "", + peer_callee_concurrent_max = "", + peer_callee_concurrent_max_ou = "", + peer_callee_outbound_socke = "", + pstn_dp_caller_in_i = "", + pstn_dp_callee_in_i = "", + pstn_dp_caller_out_i = "", + pstn_dp_callee_out_i = "", + rewrite_caller_in_dpi = "", + rewrite_caller_out_dpi = "", + rewrite_caller_out_dpi = "", + rewrite_callee_in_dpi = "", + rewrite_callee_out_dpi = "", + peer_callee_sst_enabl = "", + peer_callee_sst_expire = "", + peer_callee_sst_min_time = "", + peer_callee_sst_max_time = "", + peer_callee_sst_refresh_metho = "", + callee_outbound_from_displa = "", + callee_outbound_from_use = "", + callee_outbound_pai_use = "", + callee_outbound_ppi_use = "", + callee_outbound_diversio = "", + concurrent_ma = "", + concurrent_max_ou = "", + concurrent_max_per_accoun = "", + concurrent_max_out_per_account = "" + } + --print("NGCPPeerPrefs:init" .. "\n" .. table.tostring(t)) + return t + end + + function NGCPPeerPrefs:clean(...) + --print("NGCPPeerPrefs:clean") + --print(table.tostring(getmetatable(self))) + --print(table.tostring(self)) + NGCPPrefs.clean(self, ...) + end +-- class +--EOF \ No newline at end of file diff --git a/ngcp/pref.lua b/ngcp/pref.lua new file mode 100644 index 0000000..070c8d0 --- /dev/null +++ b/ngcp/pref.lua @@ -0,0 +1,42 @@ +#!/usr/bin/env lua5.1 +require 'kam_utils' + +-- class NGCPPrefs +NGCPPrefs = { + __class__ = 'NGCPPrefs' +} +NGCPPrefs_MT = { __index = NGCPPrefs } + + function NGCPPrefs:new() + local t = NGCPPrefs.init() + setmetatable( t, NGCPPrefs_MT ) + return t + end + + function NGCPPrefs.init() + local t = { + inbound = {}, + outbound = {}, + common = {}, + groups = {'inbound', 'outbound', 'common'} + } + --print("NGCPPrefs:init" .. "\n" .. table.tostring(t)) + return t + end + + function NGCPPrefs:clean(group) + --print("NGCPPrefs:clean") + --print(table.tostring(getmetatable(self))) + --print(table.tostring(self)) + if group then + if self[group] then + clean_avps(self[group]) + end + else + for k,v in ipairs(self.groups) do + clean_avps(self[v]) + end + end + end +-- class +--EOF \ No newline at end of file diff --git a/tests/mocks.lua b/tests/mocks.lua new file mode 100644 index 0000000..4e5f084 --- /dev/null +++ b/tests/mocks.lua @@ -0,0 +1,28 @@ +#!/usr/bin/env lua5.1 +require('luaunit') +require 'mocks.sr' + +TestMock = {} + function TestMock:testMock() + m = mc:mock() + m.pv = mc:mock() + m.titi( 42 ) + m.toto( 33, "abc", { 21} ) + end + +TestSRMock = {} + function TestSRMock:setUp() + self.sr = srMock:new() + end + + function TestSRMock:test_ini() + assertTrue(self.sr.pv) + self.sr.pv.sets("$avp('hithere')", "value") + end + +---- Control test output: +lu = LuaUnit +lu:setOutputType( "TAP" ) +lu:setVerbosity( 1 ) +lu:run() +--EOF \ No newline at end of file diff --git a/tests/ngcp.lua b/tests/ngcp.lua index 0f917c2..5af80da 100644 --- a/tests/ngcp.lua +++ b/tests/ngcp.lua @@ -1,6 +1,29 @@ #!/usr/bin/env lua5.1 require('luaunit') -require 'ngcp' +require 'ngcp.ngcp' +require 'mocks.sr' +require 'utils' + +sr = srMock:new() + +TestNGCPPrefs = {} --class + + function TestNGCPPrefs:setUp() + self.prefs = NGCPPrefs:new() + end + + function TestNGCPPrefs:test_prefs_init() + assertItemsEquals(self.prefs.groups, {"inbound","outbound","common"}) + assertTrue(self.prefs.inbound) + assertTrue(self.prefs.outbound) + assertTrue(self.prefs.common) + end + + function TestNGCPPrefs:test_pref_clean() + --self.prefs:clean() + assertError(self.prefs.clean, nil) + end +-- class TestNGCPPrefs TestNGCP = {} --class @@ -9,8 +32,24 @@ TestNGCP = {} --class end function TestNGCP:test_config() - assertEquals( self.ngcp.preference.domain.name , 'domain' ) - assertEquals( self.ngcp.preference.peer.name , 'peer' ) + assertTrue(self.ngcp.config) + end + + function TestNGCP:test_prefs_init() + --print("TestNGCP:test_prefs_init") + assertTrue(self.ngcp) + end + + function TestNGCP:test_peerpref_clean() + --print("TestNGCP:test_peerpref_clean") + assertTrue(self.ngcp.prefs.peer) + self.ngcp.prefs.peer:clean() + end + + function TestNGCP:test_domainpref_clean() + --print("TestNGCP:test_domainpref_clean") + assertTrue(self.ngcp.prefs.domain) + self.ngcp.prefs.domain:clean() end -- class TestNGCP diff --git a/tests/utils.lua b/tests/utils.lua new file mode 100644 index 0000000..7ea3195 --- /dev/null +++ b/tests/utils.lua @@ -0,0 +1,59 @@ +#!/usr/bin/env lua5.1 +require('luaunit') +require 'mocks.sr' +require 'utils' + +TestUtils = {} --class + + function TestUtils:setUp() + self.simple_hash = { + one = 1, two = 2, three = 3 + } + self.simple_list = { + 1, 2, 3 + } + self.complex_hash = { + cone = self.simple_list, + ctwo = self.simple_hash + } + end + + function TestUtils:test_table_deepcopy() + assertNotEquals(table.deepcopy(self.simple_hash), self.simple_hash) + -- if the parameter is not a table... it has te be the same + assertEquals(table.deepcopy("hola"), "hola") + end + + function TestUtils:test_table_contains() + assertTrue(table.contains(self.simple_hash, 3)) + assertFalse(table.contains(self.simple_hash, 4)) + assertFalse(table.contains(nil)) + assertError(table.contains, "hola",1) + end + + function TestUtils:test_table_tostring() + assertError(table.tostring,nil) + assertEquals(table.tostring(self.simple_list), "{1,2,3}") + assertTrue(table.tostring(self.simple_hash)) + --print(table.tostring(self.simple_hash) .. "\n") + assertTrue(table.tostring(self.complex_hash)) + --print(table.tostring(self.complex_hash)) + end + + function TestUtils:test_implode() + assertEquals(implode(',', self.simple_list, "'"), "'1','2','3'") + assertError(implode, nil, self.simple_list, "'") + assertError(implode, ',', nil, "'") + end + + function TestUtils:test_explode() + assertItemsEquals(explode(',',"1,2,3"), {'1','2','3'}) + end +-- class TestUtils + +---- Control test output: +lu = LuaUnit +lu:setOutputType( "TAP" ) +lu:setVerbosity( 1 ) +lu:run() +--EOF \ No newline at end of file diff --git a/utils.lua b/utils.lua index d6ec83e..257ed7c 100644 --- a/utils.lua +++ b/utils.lua @@ -1,36 +1,8 @@ #!/usr/bin/env lua5.1 -# Lua utils - --- kamailio log for a table -function log_table(table, msg, level) - if not level then - level = "debug" - end - if msg then - sr.log(level, msg) - end - if not table then - -- empty table - return - end - for i,v in pairs(table) do - if type(i) == "number" then - iformat = "%d" - elseif type(i) == "string" then - iformat = "%s" - end - if type(v) == "string" then - sr.log(level, string.format("i:" .. iformat .. " v: %s", i, v)) - elseif type(v) == "number" then - sr.log(level, string.format("i:" .. iformat .. " v: %d", i, v)) - elseif type(v) == "table" then - log_table(v,string.format("i:" .. iformat .. " v:", i),level) - end - end -end +-- Lua utils -- copy a table -function table_deepcopy(object) +function table.deepcopy(object) local lookup_table = {} local function _copy(object) if type(object) ~= "table" then @@ -48,6 +20,53 @@ function table_deepcopy(object) return _copy(object) end +function table.contains(table, element) + if table then + for _, value in pairs(table) do + if value == element then + return true + end + end --for + end --if + return false +end + +function table.val_to_str ( v ) + if "string" == type( v ) then + v = string.gsub( v, "\n", "\\n" ) + if string.match( string.gsub(v,"[^'\"]",""), '^"+$' ) then + return "'" .. v .. "'" + end + return '"' .. string.gsub(v,'"', '\\"' ) .. '"' + else + return "table" == type( v ) and table.tostring( v ) or + tostring( v ) + end +end + +function table.key_to_str ( k ) + if "string" == type( k ) and string.match( k, "^[_%a][_%a%d]*$" ) then + return k + else + return "[" .. table.val_to_str( k ) .. "]" + end +end + +function table.tostring( tbl ) + local result, done = {}, {} + for k, v in ipairs( tbl ) do + table.insert( result, table.val_to_str( v ) ) + done[ k ] = true + end + for k, v in pairs( tbl ) do + if not done[ k ] then + table.insert( result, + table.key_to_str( k ) .. "=" .. table.val_to_str( v ) ) + end + end + return "{" .. table.concat( result, "," ) .. "}" +end + -- from table to string -- t = {'a','b'} -- implode(",",t,"'") @@ -56,46 +75,48 @@ end -- "a#b" function implode(delimiter, list, quoter) local len = #list + if not delimiter then + error("delimiter is nil") + end if len == 0 then - return nil - end - if not quoter then - quoter = "" - end - local string = quoter .. list[1] .. quoter - for i = 2, len do - string = string .. delimiter .. quoter .. list[i] .. quoter - end - return string + return nil + end + if not quoter then + quoter = "" + end + local string = quoter .. list[1] .. quoter + for i = 2, len do + string = string .. delimiter .. quoter .. list[i] .. quoter + end + return string end -- from string to table function explode(delimiter, text) - local list = {}; local pos = 1 - if string.find("", delimiter, 1) then - -- We'll look at error handling later! - error("delimiter matches empty string!") - end - while 1 do - local first, last = string.find(text, delimiter, pos) - print (first, last) - if first then - table.insert(list, string.sub(text, pos, first-1)) - pos = last+1 - else - table.insert(list, string.sub(text, pos)) - break - end - end - return list -end + local list = {} + local pos = 1 -function compare_desc_len(a,b) - return string.len(a) > string.len(b) -end - -function findpattern(text, pattern, start) - return string.sub(text, string.find(text, pattern, start)) + if not delimiter then + error("delimiter is nil") + end + if not text then + error("text is nil") + end + if string.find("", delimiter, 1) then + -- We'll look at error handling later! + error("delimiter matches empty string!") + end + while 1 do + local first, last = string.find(text, delimiter, pos) + -- print (first, last) + if first then + table.insert(list, string.sub(text, pos, first-1)) + pos = last+1 + else + table.insert(list, string.sub(text, pos)) + break + end + end + return list end - -#EOF \ No newline at end of file +--EOF \ No newline at end of file