Вопрос

I'm attempting to test our Freeswitch lua scripts with busted and am running into a snag. The gist of it is that I need to be able to spy on code like the following

local req_host = session:getVariable('sip_req_host')
session:setVariable('curl_timeout', 0)

But I can't seem to figure out how to build the object that I should set _G.session to. The best / only good example of how to use busted I can find is at https://github.com/chris-allnutt/unit-tested-corona/blob/master/mocks/button.lua but it appears to use the same simple syntax for building a mock object that the busted docs does.

local button = {
  x = 0,
  y = 0,
  addEventListener = function() end
}

I can see how this would work for simple functions that don't need to return anything but I need to be able to get and set variables in the session object using the getVariable and setVariable functions. My simple mock object is as follows:

Session = {}
Session.__index = Session

function Session.create(params)
  local session = {}
  setmetatable(session, Session)
  session.params = params
  return session
end

function Session:getVariable(key)
  return self.params[key]
end

function Session:setVariable(key, val)
  self.params[key] = val
end

function Session:execute(cmd, code)
end

and the test is as follows

require "busted"
require("test_utils")

describe("Test voip lua script", function()
  it('Test webrtc bad domain', function()
    domain = 'rtc.baddomain.com';
    session_params = {['sip_req_host'] = domain,
                      ['sip_req_user'] = 'TEST-WebRTC-Client',
                      ["sip_from_user"] = 'testwebrtc_p_12345',
                      ['sip_call_id'] = 'test@call_id',
                      ['sip_authorized'] = 'false'}
    exec_str = 'sofia_contact TEST-WebRTC-Client@'..domain;
    api_params = {[exec_str] = 'error/user_not_registered'}

    _G.session = mock(Session.create(session_params), 'execute')
    _G.api = API.create(api_params)
    _G.freeswitch = Freeswitch.create()

    dofile("tested_script.lua")

    assert.spy(_G.session.execute).called_with("respond", "407")
  end)
end)

I end up with the following exception. /usr/local/share/lua/5.2/luassert/spy.lua:78: attempt to index a function value

This exception is being thrown by luassert, a dependency of the busted library, in the following if statement

77:local function called_with(state, arguments)
78:  if rawget(state, "payload") and rawget(state, "payload").called_with then
79:    return state.payload:called_with(arguments)
80:  else
81:    error("'called_with' must be chained after 'spy(aspy)'")
82:  end
83:end

I'm quite new to lua so it seems likely that I'm just missing some obvious part of the language but any help or pointers would be greatly appreciated.

Это было полезно?

Решение

So the answer that I found after another day of debugging was that yes, you do need to use a table as the object that you call mock on. However, because lua is a very forgiving language when it comes to building objects with callable parameters this still ends up working. I've built a wrapper around the object for reasons unrelated to this question but you can see what I ultimately made work below.

function SessionConstructor.create(params)
  local session_constructor = {}
  setmetatable(session_constructor, SessionConstructor)
  session_constructor.session = {}
  session_constructor.session.params = params
  session_constructor.session.getVariable = function(self,key)
    return self.params[key]
  end
  session_constructor.session.setVariable = function(self, key, val)
    self.params[key] = val
  end
  session_constructor.session.execute = function(self, cmd, code)
  end

  return session_constructor
end

function SessionConstructor:construct()
  return self.session
end

One important caveat, because of how you have to pass self into functions that will be called with lua's ":" syntax the method of spying on what functions was called with does change as seen in test file below.

require "busted"
require "test_utils"

describe("Test voip lua script", function()
  it('Test webrtc bad domain', function()
    domain = 'rtc.baddomain.com';
    session_params = {['sip_req_host'] = domain,
                      ['sip_req_user'] = 'TEST-WebRTC-Client',
                      ["sip_from_user"] = 'testwebrtc_p_12345',
                      ['sip_call_id'] = 'test@call_id',
                      ['sip_authorized'] = 'false'}
    local sess_con = SessionConstructor.create(session_params)

    exec_str = 'sofia_contact TEST-WebRTC-Client@'..domain;    
    local api_con = APIConstructor.create()
    api_con:expect_exec(exec_str, 'error/user_not_registered')

    _G.session = mock(sess_con:construct())
    _G.api = mock(api_con:construct())
    _G.freeswitch = create_freeswitch()

    dofile("tested_script.lua")

    assert.spy(session.execute).was.called_with(session, "respond", "407")
    assert.spy(session.execute).was_not.called_with("respond", "407") --This is unfortunate
  end)
end)

Другие советы

mod_lua in FreeSWITCH uses a slightly customized Lua interpreter, and you seem to use a different Lua interpreter that is installed on your host. I guess they won't work together easily.

I did some reverse engineering of the busted bin script, and came to the following script (let's call it runner.lua):

busted = require 'busted.core'()
local environment = require 'busted.environment'(busted.context)

function unpack(t, i)
  i = i or 1
  if t[i] ~= nil then
    return t[i], unpack(t, i + 1)
  end
end
busted.getTrace = function(element, level, msg)
    level = level or  3

    local info = debug.getinfo(level, 'Sl')
    info.traceback = debug.traceback('', level)
    info.message = msg
    if msg ~= nil then
      freeswitch.consoleLog("NOTICE", msg)
    end

    local file = busted.getFile(element)
    return file.getTrace(file.name, info)
end

busted.safe = function(descriptor, run, element, setenv)
    if setenv and (type(run) == 'function' or getmetatable(run).__call) then
      -- prioritize __call if it exists, like in files
      environment.wrap(getmetatable(run).__call or run)
    end

    busted.context.push(element)
    local trace, message

    local ret = { xpcall(run, function(msg)
      message = busted.rewriteMessage(element, msg)
      freeswitch.consoleLog("ERR", message)
      trace = busted.getTrace(element, 3, msg)
    end) }

    if not ret[1] then
      busted.publish({ 'error', descriptor }, element, busted.context.parent(element), message, trace)
    end

    busted.context.pop()
    return unpack(ret)
end
require 'busted.init'(busted)

local checkTag = function(name, tag, modifier)
  local found = name:find('#' .. tag)         
  return (modifier == (found ~= nil))         
end                                           

local checkTags = function(name)              
  for i, tag in pairs(tags) do                
    if not checkTag(name, tag, true) then     
      return nil, false                       
    end                                       
  end                                         

  for i, tag in pairs(excludeTags) do         
    if not checkTag(name, tag, false) then    
      return nil, false                       
    end                                       
  end                                         

  return nil, true                            
end          

local getTrace =  function(filename, info)      
  local index = info.traceback:find('\n%s*%[C]')
  info.traceback = info.traceback:sub(1, index) 
  return info, false                            
end                                             

local file = setmetatable({
  getTrace = getTrace
}, {
  __call = loadfile("/path/scripts/main_spec.lua")
})
busted.executors.file("main_spec.lua", file)

local failures = 0                      
local errors = 0                        

busted.subscribe({ 'error' }, function()
  errors = errors + 1                   
end)                                    

busted.subscribe({ 'test', 'end' }, function(element, parent, status)
  if status == 'failure' then                               
    failures = failures + 1                                 
  end                                                       
end)                                                        

busted.publish({ 'suite', 'start' })
busted.execute()
busted.publish({ 'suite', 'end' })
freeswitch.consoleLog("NOTICE", "Failures: " .. failures)
freeswitch.consoleLog("NOTICE", "Errors: " .. errors)

The script works against one file only, the /path/scripts/main_spec.lua thing, but still is usable. What you can do with this runner.lua script, is running it with luarun from the Freeswitch console:

fs_cli
luarun /path/to/runner.lua

And you'll get the output there.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top