local lcm = require('lcm') -- this line required to make `require` correctly search the working directory package.path = './?/init.lua;' .. package.path local lcmtest = require('lcmtest') local function info(text) print("client: " .. text) end local function check_field(value, expectedvalue, name) if value ~= expectedvalue then error(string.format("%s does not equal %s for field %s!", tostring(value), tostring(expectedvalue), name)) end end local MultidimTest = {} MultidimTest.__index = MultidimTest function MultidimTest:new() local obj = {} obj.name = "multidim array test" obj.msgtype = lcmtest.multidim_array_t obj.num_iterations = 5 setmetatable(obj, MultidimTest) return obj end function MultidimTest:make_message(iteration) local msg = self.msgtype:new() msg.size_a = iteration msg.size_b = iteration msg.size_c = iteration local n = 0 for i = 1, msg.size_a do msg.data[i] = {} for j = 1, msg.size_b do msg.data[i][j] = {} for k = 1, msg.size_c do msg.data[i][j][k] = n n = n + 1 end end end n = 0 for i = 1, 2 do msg.strarray[i] = {} for j = 1, msg.size_c do msg.strarray[i][j] = tostring(n) n = n + 1 end end return msg end function MultidimTest:check_reply(msg, iteration) check_field(msg.size_a, iteration + 1, "size_a") check_field(msg.size_b, iteration + 1, "size_b") check_field(msg.size_c, iteration + 1, "size_c") local n = 0 for i = 1, msg.size_a do for j = 1, msg.size_b do for k = 1, msg.size_c do check_field(msg.data[i][j][k], n, string.format("data[%d][%d][%d]", i, j, k)) n = n + 1 end end end n = 0 for i = 1, 2 do for j = 1, msg.size_c do check_field(msg.strarray[i][j], tostring(n), string.format("strarray[%d][%d]", i, j)) n = n + 1 end end end local NodeTest = {} NodeTest.__index = NodeTest function NodeTest:new() local obj = {} obj.name = "node test" obj.msgtype = lcmtest.node_t obj.num_iterations = 7 setmetatable(obj, NodeTest) return obj end function NodeTest:make_message(iteration) local msg = self.msgtype:new() msg.num_children = iteration msg.children = {} for i = 1, msg.num_children do msg.children[i] = self:make_message(msg.num_children - 1) end return msg end function NodeTest:check_reply(msg, iteration, descendant) if not descendant then -- this is only used for the root of the message check_field(msg.num_children, iteration + 1, "num_children") else check_field(msg.num_children, iteration, "num_children") end for _, childmsg in pairs(msg.children) do self:check_reply(childmsg, msg.num_children - 1, true) end end local PrimitivesListTest = {} PrimitivesListTest.__index = PrimitivesListTest function PrimitivesListTest:new() local obj = {} obj.name = "primitives list test" obj.msgtype = lcmtest.primitives_list_t obj.num_iterations = 100 setmetatable(obj, PrimitivesListTest) return obj end function PrimitivesListTest:make_message(iteration) local msg = self.msgtype:new() msg.num_items = iteration msg.items = {} for ix = 1, msg.num_items do local submsg = lcmtest.primitives_t:new() -- make i start at zero local i = ix - 1 submsg.i8 = -(i % 100) submsg.i16 = -i * 10 submsg.i64 = -i * 10000 submsg.position[1] = -i submsg.position[2] = -i submsg.position[3] = -i submsg.orientation[1] = -i submsg.orientation[2] = -i submsg.orientation[3] = -i submsg.orientation[4] = -i submsg.num_ranges = i submsg.ranges = {} for j = 1, submsg.num_ranges do submsg.ranges[j] = -(j - 1) end -- +0 is to avoid issues with tostring(-0) submsg.name = tostring(-i + 0) -- because Lua evaluates 0 as true submsg.enabled = (((i + 1) % 2) ~= 0) msg.items[ix] = submsg end return msg end function PrimitivesListTest:check_reply(msg, iteration) check_field(msg.num_items, iteration + 1, "num_items") for ix = 1, msg.num_items do local submsg = msg.items[ix] local prefix = string.format("items[%d].", ix) -- make i start at zero local i = ix - 1 check_field(submsg.i8, -(i % 100), prefix .. "i8") check_field(submsg.i16, -(i * 10), prefix .. "i16") check_field(submsg.i64, -(i * 10000), prefix .. "i64") check_field(submsg.position[1], -i, prefix .. "position[1]") check_field(submsg.position[2], -i, prefix .. "position[2]") check_field(submsg.position[3], -i, prefix .. "position[3]") check_field(submsg.orientation[1], -i, prefix .. "orientation[1]") check_field(submsg.orientation[2], -i, prefix .. "orientation[2]") check_field(submsg.orientation[3], -i, prefix .. "orientation[3]") check_field(submsg.orientation[4], -i, prefix .. "orientation[4]") check_field(submsg.num_ranges, i, prefix .. "num_ranges") for j = 1, submsg.num_ranges do check_field(submsg.ranges[j], -(j - 1), prefix .. string.format("ranges[%d]", j)) end -- +0 is to avoid issues with tostring(-0) check_field(submsg.name, tostring(-i + 0), prefix .. "name") -- because Lua evaluates 0 as true check_field(submsg.enabled, (((i + 1) % 2) ~= 0), prefix .. "enabled") end end local PrimitivesTest = {} PrimitivesTest.__index = PrimitivesTest function PrimitivesTest:new() local obj = {} obj.name = "primitives test" obj.msgtype = lcmtest.primitives_t obj.num_iterations = 1000 setmetatable(obj, PrimitivesTest) return obj end function PrimitivesTest:make_message(iteration) local msg = self.msgtype:new() local n = iteration msg.i8 = n % 100 msg.i16 = n * 10 msg.i64 = n * 10000 msg.position[1] = n msg.position[2] = n msg.position[3] = n msg.orientation[1] = n msg.orientation[2] = n msg.orientation[3] = n msg.orientation[4] = n msg.num_ranges = n msg.ranges = {} for i = 1, msg.num_ranges do msg.ranges[i] = i - 1 end msg.name = tostring(n) -- because Lua evaluates 0 as true msg.enabled = ((n % 2) ~= 0) return msg end function PrimitivesTest:check_reply(msg, iteration) local n = iteration check_field(msg.i8, (n + 1) % 100, "i8") check_field(msg.i16, (n + 1) * 10, "i16"); check_field(msg.i64, (n + 1) * 10000, "i64"); check_field(msg.position[1], n + 1, "position[1]"); check_field(msg.position[2], n + 1, "position[2]"); check_field(msg.position[3], n + 1, "position[3]"); check_field(msg.orientation[1], n + 1, "orientation[1]"); check_field(msg.orientation[2], n + 1, "orientation[2]"); check_field(msg.orientation[3], n + 1, "orientation[3]"); check_field(msg.orientation[4], n + 1, "orientation[4]"); check_field(msg.num_ranges, n + 1, "num_ranges"); for i = 1, n + 1 do check_field(msg.ranges[i], i - 1, string.format("ranges[%d]", i)) end check_field(msg.name, tostring(n + 1), "name") -- because Lua evaluates 0 as true check_field(msg.enabled, (((n + 1) % 2) ~= 0), "enabled") end local StandardTester = {} StandardTester.__index = StandardTester function StandardTester:new(lc, test) local obj = {} obj.lc = lc obj.test = test obj.testerr = nil obj.response_count = 0 obj.iteration = 0 setmetatable(obj, StandardTester) return obj end function StandardTester:handler(channel, data) local function process_msg() -- this will error if something goes wrong self.test:check_reply(self.test.msgtype.decode(data), self.iteration) end local function process_err(argerr) -- set the objects error, testerr self.testerr = argerr end -- discard status, run() will check testerr xpcall(process_msg, process_err) end function StandardTester:run() self.testerr = nil self.response_count = 0 self.iteration = 0 local channel = string.format("test_lcmtest_%s", self.test.msgtype.shortname) local reply_channel = string.format("test_lcmtest_%s_reply", self.test.msgtype.shortname) local handler_closure = function(channel, data) self:handler(channel, data) end local sub = self.lc:subscribe(reply_channel, handler_closure) for i = 1, self.test.num_iterations do -- send a message local msg = self.test:make_message(self.iteration) self.lc:publish(channel, msg:encode()) -- get the message, check result local ontime = self.lc:timedhandle(500000) if not ontime then self.testerr = "timed out waiting for response!" end -- check for an error if self.testerr then info(string.format("%s failed on iteration %d!", self.test.name, self.iteration)) info("error: " .. tostring(self.testerr)) self.lc:unsubscribe(sub) return false end self.iteration = self.iteration + 1 end info(string.format("lcmtest_%-17s : PASSED :-)", self.test.msgtype.shortname)) self.lc:unsubscribe(sub) return true end local EchoTester = {} EchoTester.__index = EchoTester function EchoTester:new(lc) local obj = {} obj.lc = lc obj.data = nil obj.response_count = 0 obj.num_iterations = 100 setmetatable(obj, EchoTester) return obj end function EchoTester:handler(channel, data) if data == self.data then self.response_count = self.response_count + 1 end end function EchoTester:run() local handler_closure = function(channel, data) self:handler(channel, data) end local sub = self.lc:subscribe("TEST_ECHO_REPLY", handler_closure) for i = 1, self.num_iterations do local datalen = math.random(10, 10000) local chars = {} for j = 1, datalen do chars[j] = string.char(math.random(0, 255)) end self.data = table.concat(chars) self.lc:publish("TEST_ECHO", self.data) if not self.lc:timedhandle(500000) or self.response_count ~= i then info(string.format("echo test failed to receive a response on iteration %d!", i)) self.lc:unsubscribe(sub) return false end end info(string.format("%-25s : PASSED :-)", "echo test")) self.lc:unsubscribe(sub) return true end local Tester = {} Tester.__index = Tester function Tester:new() local obj = {} setmetatable(obj, Tester) return obj end function Tester:run() -- make lcm connection local lc = lcm.lcm.new() -- run all tests EchoTester:new(lc):run() StandardTester:new(lc, PrimitivesTest:new()):run() StandardTester:new(lc, PrimitivesListTest:new()):run() StandardTester:new(lc, NodeTest:new()):run() StandardTester:new(lc, MultidimTest:new()):run() -- attempt to garbage collect the lcm connection lc = nil for i = 1, 1000 do collectgarbage() end info("All tests passed.") end -- now to actually do something local test = Tester:new() test:run()