--[[ luaunit.lua Description: A unit testing framework Homepage: https://github.com/bluebird75/luaunit Development by Philippe Fremy Based on initial work of Ryu, Gwang (http://www.gpgstudy.com/gpgiki/LuaUnit) License: BSD License, see LICENSE.txt ]]-- require("math") local M={} -- private exported functions (for testing) M.private = {} M.VERSION='3.3' M._VERSION=M.VERSION -- For LuaUnit v2 compatibility -- a version which distinguish between regular Lua and LuaJit M._LUAVERSION = (jit and jit.version) or _VERSION --[[ Some people like assertEquals( actual, expected ) and some people prefer assertEquals( expected, actual ). ]]-- M.ORDER_ACTUAL_EXPECTED = true M.PRINT_TABLE_REF_IN_ERROR_MSG = false M.TABLE_EQUALS_KEYBYCONTENT = true M.LINE_LENGTH = 80 M.TABLE_DIFF_ANALYSIS_THRESHOLD = 10 -- display deep analysis for more than 10 items M.LIST_DIFF_ANALYSIS_THRESHOLD = 10 -- display deep analysis for more than 10 items --[[ EPS is meant to help with Lua's floating point math in simple corner cases like almostEquals(1.1-0.1, 1), which may not work as-is (e.g. on numbers with rational binary representation) if the user doesn't provide some explicit error margin. The default margin used by almostEquals() in such cases is EPS; and since Lua may be compiled with different numeric precisions (single vs. double), we try to select a useful default for it dynamically. Note: If the initial value is not acceptable, it can be changed by the user to better suit specific needs. See also: https://en.wikipedia.org/wiki/Machine_epsilon ]] M.EPS = 2^-52 -- = machine epsilon for "double", ~2.22E-16 if math.abs(1.1 - 1 - 0.1) > M.EPS then -- rounding error is above EPS, assume single precision M.EPS = 2^-23 -- = machine epsilon for "float", ~1.19E-07 end -- set this to false to debug luaunit local STRIP_LUAUNIT_FROM_STACKTRACE = true M.VERBOSITY_DEFAULT = 10 M.VERBOSITY_LOW = 1 M.VERBOSITY_QUIET = 0 M.VERBOSITY_VERBOSE = 20 M.DEFAULT_DEEP_ANALYSIS = nil M.FORCE_DEEP_ANALYSIS = true M.DISABLE_DEEP_ANALYSIS = false -- set EXPORT_ASSERT_TO_GLOBALS to have all asserts visible as global values -- EXPORT_ASSERT_TO_GLOBALS = true -- we need to keep a copy of the script args before it is overriden local cmdline_argv = rawget(_G, "arg") M.FAILURE_PREFIX = 'LuaUnit test FAILURE: ' -- prefix string for failed tests M.SUCCESS_PREFIX = 'LuaUnit test SUCCESS: ' -- prefix string for successful tests finished early M.SKIP_PREFIX = 'LuaUnit test SKIP: ' -- prefix string for skipped tests M.USAGE=[[Usage: lua [options] [testname1 [testname2] ... ] Options: -h, --help: Print this help --version: Print version information -v, --verbose: Increase verbosity -q, --quiet: Set verbosity to minimum -e, --error: Stop on first error -f, --failure: Stop on first failure or error -s, --shuffle: Shuffle tests before running them -o, --output OUTPUT: Set output type to OUTPUT Possible values: text, tap, junit, nil -n, --name NAME: For junit only, mandatory name of xml file -r, --repeat NUM: Execute all tests NUM times, e.g. to trig the JIT -p, --pattern PATTERN: Execute all test names matching the Lua PATTERN May be repeated to include several patterns Make sure you escape magic chars like +? with % -x, --exclude PATTERN: Exclude all test names matching the Lua PATTERN May be repeated to exclude several patterns Make sure you escape magic chars like +? with % testname1, testname2, ... : tests to run in the form of testFunction, TestClass or TestClass.testMethod ]] local is_equal -- defined here to allow calling from mismatchFormattingPureList ---------------------------------------------------------------- -- -- general utility functions -- ---------------------------------------------------------------- local function pcall_or_abort(func, ...) -- unpack is a global function for Lua 5.1, otherwise use table.unpack local unpack = rawget(_G, "unpack") or table.unpack local result = {pcall(func, ...)} if not result[1] then -- an error occurred print(result[2]) -- error message print() print(M.USAGE) os.exit(-1) end return unpack(result, 2) end local crossTypeOrdering = { number = 1, boolean = 2, string = 3, table = 4, other = 5 } local crossTypeComparison = { number = function(a, b) return a < b end, string = function(a, b) return a < b end, other = function(a, b) return tostring(a) < tostring(b) end, } local function crossTypeSort(a, b) local type_a, type_b = type(a), type(b) if type_a == type_b then local func = crossTypeComparison[type_a] or crossTypeComparison.other return func(a, b) end type_a = crossTypeOrdering[type_a] or crossTypeOrdering.other type_b = crossTypeOrdering[type_b] or crossTypeOrdering.other return type_a < type_b end local function __genSortedIndex( t ) -- Returns a sequence consisting of t's keys, sorted. local sortedIndex = {} for key,_ in pairs(t) do table.insert(sortedIndex, key) end table.sort(sortedIndex, crossTypeSort) return sortedIndex end M.private.__genSortedIndex = __genSortedIndex local function sortedNext(state, control) -- Equivalent of the next() function of table iteration, but returns the -- keys in sorted order (see __genSortedIndex and crossTypeSort). -- The state is a temporary variable during iteration and contains the -- sorted key table (state.sortedIdx). It also stores the last index (into -- the keys) used by the iteration, to find the next one quickly. local key --print("sortedNext: control = "..tostring(control) ) if control == nil then -- start of iteration state.count = #state.sortedIdx state.lastIdx = 1 key = state.sortedIdx[1] return key, state.t[key] end -- normally, we expect the control variable to match the last key used if control ~= state.sortedIdx[state.lastIdx] then -- strange, we have to find the next value by ourselves -- the key table is sorted in crossTypeSort() order! -> use bisection local lower, upper = 1, state.count repeat state.lastIdx = math.modf((lower + upper) / 2) key = state.sortedIdx[state.lastIdx] if key == control then break -- key found (and thus prev index) end if crossTypeSort(key, control) then -- key < control, continue search "right" (towards upper bound) lower = state.lastIdx + 1 else -- key > control, continue search "left" (towards lower bound) upper = state.lastIdx - 1 end until lower > upper if lower > upper then -- only true if the key wasn't found, ... state.lastIdx = state.count -- ... so ensure no match in code below end end -- proceed by retrieving the next value (or nil) from the sorted keys state.lastIdx = state.lastIdx + 1 key = state.sortedIdx[state.lastIdx] if key then return key, state.t[key] end -- getting here means returning `nil`, which will end the iteration end local function sortedPairs(tbl) -- Equivalent of the pairs() function on tables. Allows to iterate in -- sorted order. As required by "generic for" loops, this will return the -- iterator (function), an "invariant state", and the initial control value. -- (see http://www.lua.org/pil/7.2.html) return sortedNext, {t = tbl, sortedIdx = __genSortedIndex(tbl)}, nil end M.private.sortedPairs = sortedPairs -- seed the random with a strongly varying seed math.randomseed(os.clock()*1E11) local function randomizeTable( t ) -- randomize the item orders of the table t for i = #t, 2, -1 do local j = math.random(i) if i ~= j then t[i], t[j] = t[j], t[i] end end end M.private.randomizeTable = randomizeTable local function strsplit(delimiter, text) -- Split text into a list consisting of the strings in text, separated -- by strings matching delimiter (which may _NOT_ be a pattern). -- Example: strsplit(", ", "Anna, Bob, Charlie, Dolores") if delimiter == "" or delimiter == nil then -- this would result in endless loops error("delimiter is nil or empty string!") end if text == nil then return nil end local list, pos, first, last = {}, 1 while true do first, last = text:find(delimiter, pos, true) if first then -- found? table.insert(list, text:sub(pos, first - 1)) pos = last + 1 else table.insert(list, text:sub(pos)) break end end return list end M.private.strsplit = strsplit local function hasNewLine( s ) -- return true if s has a newline return (string.find(s, '\n', 1, true) ~= nil) end M.private.hasNewLine = hasNewLine local function prefixString( prefix, s ) -- Prefix all the lines of s with prefix return prefix .. string.gsub(s, '\n', '\n' .. prefix) end M.private.prefixString = prefixString local function strMatch(s, pattern, start, final ) -- return true if s matches completely the pattern from index start to index end -- return false in every other cases -- if start is nil, matches from the beginning of the string -- if final is nil, matches to the end of the string start = start or 1 final = final or string.len(s) local foundStart, foundEnd = string.find(s, pattern, start, false) return foundStart == start and foundEnd == final end M.private.strMatch = strMatch local function patternFilter(patterns, expr) -- Run `expr` through the inclusion and exclusion rules defined in patterns -- and return true if expr shall be included, false for excluded. -- Inclusion pattern are defined as normal patterns, exclusions -- patterns start with `!` and are followed by a normal pattern -- result: nil = UNKNOWN (not matched yet), true = ACCEPT, false = REJECT -- default: true if no explicit "include" is found, set to false otherwise local default, result = true, nil if patterns ~= nil then for _, pattern in ipairs(patterns) do local exclude = pattern:sub(1,1) == '!' if exclude then pattern = pattern:sub(2) else -- at least one include pattern specified, a match is required default = false end -- print('pattern: ',pattern) -- print('exclude: ',exclude) -- print('default: ',default) if string.find(expr, pattern) then -- set result to false when excluding, true otherwise result = not exclude end end end if result ~= nil then return result end return default end M.private.patternFilter = patternFilter local function xmlEscape( s ) -- Return s escaped for XML attributes -- escapes table: -- " " -- ' ' -- < < -- > > -- & & return string.gsub( s, '.', { ['&'] = "&", ['"'] = """, ["'"] = "'", ['<'] = "<", ['>'] = ">", } ) end M.private.xmlEscape = xmlEscape local function xmlCDataEscape( s ) -- Return s escaped for CData section, escapes: "]]>" return string.gsub( s, ']]>', ']]>' ) end M.private.xmlCDataEscape = xmlCDataEscape local function stripLuaunitTrace( stackTrace ) --[[ -- Example of a traceback: < [C]: in function 'xpcall' ./luaunit.lua:1449: in function 'protectedCall' ./luaunit.lua:1508: in function 'execOneFunction' ./luaunit.lua:1596: in function 'runSuiteByInstances' ./luaunit.lua:1660: in function 'runSuiteByNames' ./luaunit.lua:1736: in function 'runSuite' example_with_luaunit.lua:140: in main chunk [C]: in ?>> Other example: < [C]: in function 'xpcall' ./luaunit.lua:1517: in function 'protectedCall' ./luaunit.lua:1578: in function 'execOneFunction' ./luaunit.lua:1677: in function 'runSuiteByInstances' ./luaunit.lua:1730: in function 'runSuiteByNames' ./luaunit.lua:1806: in function 'runSuite' example_with_luaunit.lua:140: in main chunk [C]: in ?>> < [C]: in function 'xpcall' luaunit2/luaunit.lua:1532: in function 'protectedCall' luaunit2/luaunit.lua:1591: in function 'execOneFunction' luaunit2/luaunit.lua:1679: in function 'runSuiteByInstances' luaunit2/luaunit.lua:1743: in function 'runSuiteByNames' luaunit2/luaunit.lua:1819: in function 'runSuite' luaunit2/example_with_luaunit.lua:140: in main chunk [C]: in ?>> -- first line is "stack traceback": KEEP -- next line may be luaunit line: REMOVE -- next lines are call in the program under testOk: REMOVE -- next lines are calls from luaunit to call the program under test: KEEP -- Strategy: -- keep first line -- remove lines that are part of luaunit -- kepp lines until we hit a luaunit line ]] local function isLuaunitInternalLine( s ) -- return true if line of stack trace comes from inside luaunit return s:find('[/\\]luaunit%.lua:%d+: ') ~= nil end -- print( '<<'..stackTrace..'>>' ) local t = strsplit( '\n', stackTrace ) -- print( prettystr(t) ) local idx = 2 -- remove lines that are still part of luaunit while t[idx] and isLuaunitInternalLine( t[idx] ) do -- print('Removing : '..t[idx] ) table.remove(t, idx) end -- keep lines until we hit luaunit again while t[idx] and (not isLuaunitInternalLine(t[idx])) do -- print('Keeping : '..t[idx] ) idx = idx + 1 end -- remove remaining luaunit lines while t[idx] do -- print('Removing : '..t[idx] ) table.remove(t, idx) end -- print( prettystr(t) ) return table.concat( t, '\n') end M.private.stripLuaunitTrace = stripLuaunitTrace local function prettystr_sub(v, indentLevel, printTableRefs, recursionTable ) local type_v = type(v) if "string" == type_v then -- use clever delimiters according to content: -- enclose with single quotes if string contains ", but no ' if v:find('"', 1, true) and not v:find("'", 1, true) then return "'" .. v .. "'" end -- use double quotes otherwise, escape embedded " return '"' .. v:gsub('"', '\\"') .. '"' elseif "table" == type_v then --if v.__class__ then -- return string.gsub( tostring(v), 'table', v.__class__ ) --end return M.private._table_tostring(v, indentLevel, printTableRefs, recursionTable) elseif "number" == type_v then -- eliminate differences in formatting between various Lua versions if v ~= v then return "#NaN" -- "not a number" end if v == math.huge then return "#Inf" -- "infinite" end if v == -math.huge then return "-#Inf" end if _VERSION == "Lua 5.3" then local i = math.tointeger(v) if i then return tostring(i) end end end return tostring(v) end local function prettystr( v ) --[[ Pretty string conversion, to display the full content of a variable of any type. * string are enclosed with " by default, or with ' if string contains a " * tables are expanded to show their full content, with indentation in case of nested tables ]]-- local recursionTable = {} local s = prettystr_sub(v, 1, M.PRINT_TABLE_REF_IN_ERROR_MSG, recursionTable) if recursionTable.recursionDetected and not M.PRINT_TABLE_REF_IN_ERROR_MSG then -- some table contain recursive references, -- so we must recompute the value by including all table references -- else the result looks like crap recursionTable = {} s = prettystr_sub(v, 1, true, recursionTable) end return s end M.prettystr = prettystr function M.adjust_err_msg_with_iter( err_msg, iter_msg ) --[[ Adjust the error message err_msg: trim the FAILURE_PREFIX or SUCCESS_PREFIX information if needed, add the iteration message if any and return the result. err_msg: string, error message captured with pcall iter_msg: a string describing the current iteration ("iteration N") or nil if there is no iteration in this test. Returns: (new_err_msg, test_status) new_err_msg: string, adjusted error message, or nil in case of success test_status: M.NodeStatus.FAIL, SUCCESS or ERROR according to the information contained in the error message. ]] if iter_msg then iter_msg = iter_msg..', ' else iter_msg = '' end local RE_FILE_LINE = '.*:%d+: ' -- error message is not necessarily a string, -- so convert the value to string with prettystr() if type( err_msg ) ~= 'string' then err_msg = prettystr( err_msg ) end if (err_msg:find( M.SUCCESS_PREFIX ) == 1) or err_msg:match( '('..RE_FILE_LINE..')' .. M.SUCCESS_PREFIX .. ".*" ) then -- test finished early with success() return nil, M.NodeStatus.SUCCESS end if (err_msg:find( M.SKIP_PREFIX ) == 1) or (err_msg:match( '('..RE_FILE_LINE..')' .. M.SKIP_PREFIX .. ".*" ) ~= nil) then -- substitute prefix by iteration message err_msg = err_msg:gsub('.*'..M.SKIP_PREFIX, iter_msg, 1) -- print("failure detected") return err_msg, M.NodeStatus.SKIP end if (err_msg:find( M.FAILURE_PREFIX ) == 1) or (err_msg:match( '('..RE_FILE_LINE..')' .. M.FAILURE_PREFIX .. ".*" ) ~= nil) then -- substitute prefix by iteration message err_msg = err_msg:gsub(M.FAILURE_PREFIX, iter_msg, 1) -- print("failure detected") return err_msg, M.NodeStatus.FAIL end -- print("error detected") -- regular error, not a failure if iter_msg then local match -- "./test\\test_luaunit.lua:2241: some error msg match = err_msg:match( '(.*:%d+: ).*' ) if match then err_msg = err_msg:gsub( match, match .. iter_msg ) else -- no file:line: infromation, just add the iteration info at the beginning of the line err_msg = iter_msg .. err_msg end end return err_msg, M.NodeStatus.ERROR end local function tryMismatchFormatting( table_a, table_b, doDeepAnalysis ) --[[ Prepares a nice error message when comparing tables, performing a deeper analysis. Arguments: * table_a, table_b: tables to be compared * doDeepAnalysis: M.DEFAULT_DEEP_ANALYSIS: (the default if not specified) perform deep analysis only for big lists and big dictionnaries M.FORCE_DEEP_ANALYSIS : always perform deep analysis M.DISABLE_DEEP_ANALYSIS: never perform deep analysis Returns: {success, result} * success: false if deep analysis could not be performed in this case, just use standard assertion message * result: if success is true, a multi-line string with deep analysis of the two lists ]] -- check if table_a & table_b are suitable for deep analysis if type(table_a) ~= 'table' or type(table_b) ~= 'table' then return false end if doDeepAnalysis == M.DISABLE_DEEP_ANALYSIS then return false end local len_a, len_b, isPureList = #table_a, #table_b, true for k1, v1 in pairs(table_a) do if type(k1) ~= 'number' or k1 > len_a then -- this table a mapping isPureList = false break end end if isPureList then for k2, v2 in pairs(table_b) do if type(k2) ~= 'number' or k2 > len_b then -- this table a mapping isPureList = false break end end end if isPureList and math.min(len_a, len_b) < M.LIST_DIFF_ANALYSIS_THRESHOLD then if not (doDeepAnalysis == M.FORCE_DEEP_ANALYSIS) then return false end end if isPureList then return M.private.mismatchFormattingPureList( table_a, table_b ) else -- only work on mapping for the moment -- return M.private.mismatchFormattingMapping( table_a, table_b, doDeepAnalysis ) return false end end M.private.tryMismatchFormatting = tryMismatchFormatting local function getTaTbDescr() if not M.ORDER_ACTUAL_EXPECTED then return 'expected', 'actual' end return 'actual', 'expected' end local function extendWithStrFmt( res, ... ) table.insert( res, string.format( ... ) ) end local function mismatchFormattingMapping( table_a, table_b, doDeepAnalysis ) --[[ Prepares a nice error message when comparing tables which are not pure lists, performing a deeper analysis. Returns: {success, result} * success: false if deep analysis could not be performed in this case, just use standard assertion message * result: if success is true, a multi-line string with deep analysis of the two lists ]] -- disable for the moment --[[ local result = {} local descrTa, descrTb = getTaTbDescr() local keysCommon = {} local keysOnlyTa = {} local keysOnlyTb = {} local keysDiffTaTb = {} local k, v for k,v in pairs( table_a ) do if is_equal( v, table_b[k] ) then table.insert( keysCommon, k ) else if table_b[k] == nil then table.insert( keysOnlyTa, k ) else table.insert( keysDiffTaTb, k ) end end end for k,v in pairs( table_b ) do if not is_equal( v, table_a[k] ) and table_a[k] == nil then table.insert( keysOnlyTb, k ) end end local len_a = #keysCommon + #keysDiffTaTb + #keysOnlyTa local len_b = #keysCommon + #keysDiffTaTb + #keysOnlyTb local limited_display = (len_a < 5 or len_b < 5) if math.min(len_a, len_b) < M.TABLE_DIFF_ANALYSIS_THRESHOLD then return false end if not limited_display then if len_a == len_b then extendWithStrFmt( result, 'Table A (%s) and B (%s) both have %d items', descrTa, descrTb, len_a ) else extendWithStrFmt( result, 'Table A (%s) has %d items and table B (%s) has %d items', descrTa, len_a, descrTb, len_b ) end if #keysCommon == 0 and #keysDiffTaTb == 0 then table.insert( result, 'Table A and B have no keys in common, they are totally different') else local s_other = 'other ' if #keysCommon then extendWithStrFmt( result, 'Table A and B have %d identical items', #keysCommon ) else table.insert( result, 'Table A and B have no identical items' ) s_other = '' end if #keysDiffTaTb ~= 0 then result[#result] = string.format( '%s and %d items differing present in both tables', result[#result], #keysDiffTaTb) else result[#result] = string.format( '%s and no %sitems differing present in both tables', result[#result], s_other, #keysDiffTaTb) end end extendWithStrFmt( result, 'Table A has %d keys not present in table B and table B has %d keys not present in table A', #keysOnlyTa, #keysOnlyTb ) end local function keytostring(k) if "string" == type(k) and k:match("^[_%a][_%w]*$") then return k end return prettystr(k) end if #keysDiffTaTb ~= 0 then table.insert( result, 'Items differing in A and B:') for k,v in sortedPairs( keysDiffTaTb ) do extendWithStrFmt( result, ' - A[%s]: %s', keytostring(v), prettystr(table_a[v]) ) extendWithStrFmt( result, ' + B[%s]: %s', keytostring(v), prettystr(table_b[v]) ) end end if #keysOnlyTa ~= 0 then table.insert( result, 'Items only in table A:' ) for k,v in sortedPairs( keysOnlyTa ) do extendWithStrFmt( result, ' - A[%s]: %s', keytostring(v), prettystr(table_a[v]) ) end end if #keysOnlyTb ~= 0 then table.insert( result, 'Items only in table B:' ) for k,v in sortedPairs( keysOnlyTb ) do extendWithStrFmt( result, ' + B[%s]: %s', keytostring(v), prettystr(table_b[v]) ) end end if #keysCommon ~= 0 then table.insert( result, 'Items common to A and B:') for k,v in sortedPairs( keysCommon ) do extendWithStrFmt( result, ' = A and B [%s]: %s', keytostring(v), prettystr(table_a[v]) ) end end return true, table.concat( result, '\n') ]] end M.private.mismatchFormattingMapping = mismatchFormattingMapping local function mismatchFormattingPureList( table_a, table_b ) --[[ Prepares a nice error message when comparing tables which are lists, performing a deeper analysis. Returns: {success, result} * success: false if deep analysis could not be performed in this case, just use standard assertion message * result: if success is true, a multi-line string with deep analysis of the two lists ]] local result, descrTa, descrTb = {}, getTaTbDescr() local len_a, len_b, refa, refb = #table_a, #table_b, '', '' if M.PRINT_TABLE_REF_IN_ERROR_MSG then refa, refb = string.format( '<%s> ', tostring(table_a)), string.format('<%s> ', tostring(table_b) ) end local longest, shortest = math.max(len_a, len_b), math.min(len_a, len_b) local delta
/*
 * Copyright (C) 2016, 2017 "IoT.bzh"
 * Author José Bollo <jose.bollo@iot.bzh>
 *
 * 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.
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <json-c/json.h>

#include <systemd/sd-bus.h>
#include <systemd/sd-bus-protocol.h>

#define AFB_BINDING_VERSION 1
#include <afb/afb-binding.h>

/*
 * the interface to afb-daemon
 */
const struct afb_binding_interface *afbitf;

/*
 * union of possible dbus values
 */
union any {
	uint8_t u8;
	int16_t i16;
	uint16_t u16;
	int32_t i32;
	uint32_t u32;
	int64_t i64;
	uint64_t u64;
	double dbl;
	const char *cstr;
	char *str;
};

static int unpacklist(struct sd_bus_message *msg, struct json_object **result);
static int packlist(struct sd_bus_message *msg, const char *signature, struct json_object *list);

/*
 * Get the string of 'key' from 'obj'
 * Returns NULL if 'key' isn't in 'obj'
 */
static const char *strval(struct json_object *obj, const char *key)
{
	struct json_object *keyval;
	return json_object_object_get_ex(obj, key, &keyval) ? json_object_get_string(keyval) : NULL;
}

/*
 * Signature of a json object
 */
static const char *signature_for_json(struct json_object *obj)
{
	switch (json_object_get_type(obj)) {
	default:
	case json_type_null:
		return NULL;
	case json_type_boolean:
		return "b";
	case json_type_double:
		return "d";
	case json_type_int:
		return "i";
	case json_type_object:
		return "a{sv}";
	case json_type_array:
		return "av";
	case json_type_string:
		return "s";
	}
}

/*
 * Length of a single complete type
 */
static int lentype(const char *signature, int allows_dict, int allows_not_basic)
{
	int rc, len;
	switch(signature[0]) {

	case SD_BUS_TYPE_ARRAY:
		if (!allows_not_basic)
			break;
		rc = lentype(signature + 1, 1, 1);
		if (rc < 0)
			break;
		return 1 + rc;

	case SD_BUS_TYPE_STRUCT_BEGIN:
		if (!allows_not_basic)
			break;
		len = 1;
		rc = lentype(signature + len, 0, 1);
		while (rc > 0 && signature[len] != SD_BUS_TYPE_STRUCT_END) {
			len += rc;
			rc = lentype(signature + len, 0, 1);
		}
		if (rc < 0)
			break;
		return 1 + len;

	case SD_BUS_TYPE_DICT_ENTRY_BEGIN:
		if (!allows_not_basic || !allows_dict)
			break;
		rc = lentype(signature + 1, 0, 0);
		if (rc < 0)
			break;
		len = 1 + rc;
		rc = lentype(signature + len, 0, 1);
		if (rc < 0 || signature[len + rc] != SD_BUS_TYPE_DICT_ENTRY_END)
			break;
		return len + rc + 1;

	case '\x0':
	case SD_BUS_TYPE_STRUCT:
	case SD_BUS_TYPE_STRUCT_END:
	case SD_BUS_TYPE_DICT_ENTRY:
	case SD_BUS_TYPE_DICT_ENTRY_END:
		break;

	default:
		return 1;
	}
	return -1;
}


/*
 * Unpack a D-Bus message to a json object
 */
static int unpacksingle(struct sd_bus_message *msg, struct json_object **result)
{
	char c;
	int rc;
	union any any;
	const char *content;
	struct json_object *item;

	*result = NULL;
	rc = sd_bus_message_peek_type(msg, &c, &content);
	if (rc <= 0)
		return rc;

	switch (c) {
	case SD_BUS_TYPE_BYTE:
	case SD_BUS_TYPE_BOOLEAN:
	case SD_BUS_TYPE_INT16:
	case SD_BUS_TYPE_UINT16:
	case SD_BUS_TYPE_INT32:
	case SD_BUS_TYPE_UINT32:
	case SD_BUS_TYPE_INT64:
	case SD_BUS_TYPE_UINT64:
	case SD_BUS_TYPE_DOUBLE:
	case SD_BUS_TYPE_STRING:
	case SD_BUS_TYPE_OBJECT_PATH:
	case SD_BUS_TYPE_SIGNATURE:
		rc = sd_bus_message_read_basic(msg, c, &any);
		if (rc < 0)
			goto error;
		switch (c) {
		case SD_BUS_TYPE_BOOLEAN:
			*result = json_object_new_boolean(any.i32);
			break;
		case SD_BUS_TYPE_BYTE:
			*result = json_object_new_int(any.u8);
			break;
		case SD_BUS_TYPE_INT16:
			*result = json_object_new_int(any.i16);
			break;
		case SD_BUS_TYPE_UINT16:
			*result = json_object_new_int(any.u16);
			break;
		case SD_BUS_TYPE_INT32:
			*result = json_object_new_int(any.i32);
			break;
		case SD_BUS_TYPE_UINT32:
			*result = json_object_new_int64(any.u32);
			break;
		case SD_BUS_TYPE_INT64:
			*result = json_object_new_int64(any.i64);
			break;
		case SD_BUS_TYPE_UINT64:
			*result = json_object_new_int64((int64_t)any.u64);
			break;
		case SD_BUS_TYPE_DOUBLE:
			*result = json_object_new_string(any.cstr);
			break;
		case SD_BUS_TYPE_STRING:
		case SD_BUS_TYPE_OBJECT_PATH:
		case SD_BUS_TYPE_SIGNATURE:
			*result = json_object_new_string(any.cstr);
			break;
		}
		return *result == NULL ? -1 : 1;

	case SD_BUS_TYPE_ARRAY:
	case SD_BUS_TYPE_VARIANT:
	case SD_BUS_TYPE_STRUCT:
	case SD_BUS_TYPE_DICT_ENTRY:
		rc = sd_bus_message_enter_container(msg, c, content);
		if (rc < 0)
			goto error;
		if (c == SD_BUS_TYPE_ARRAY && content[0] == SD_BUS_TYPE_DICT_ENTRY_BEGIN && content[1] == SD_BUS_TYPE_STRING) {
			*result = json_object_new_object();
			if (*result == NULL)
				return -1;
			for(;;) {
				rc = sd_bus_message_enter_container(msg, 0, NULL);
				if (rc < 0)
					goto error;
				if (rc == 0)
					break;
				rc = sd_bus_message_read_basic(msg, SD_BUS_TYPE_STRING, &any);
				if (rc < 0)
					goto error;
				rc = unpacksingle(msg, &item);
				if (rc < 0)
					goto error;
				json_object_object_add(*result, any.cstr, item);
				rc = sd_bus_message_exit_container(msg);
				if (rc < 0)
					goto error;
			}
		} else {
			rc = unpacklist(msg, result);
			if (rc < 0)
				goto error;
		}
		rc = sd_bus_message_exit_container(msg);
		if (rc < 0)
			goto error;
		return 1;
	default:
		goto error;
	}
error:
	json_object_put(*result);
	return -1;
}

/*
 * Unpack a D-Bus message to a json object
 */
static int unpacklist(struct sd_bus_message *msg, struct json_object **result)
{
	int rc;
	struct json_object *item;

	/* allocates the result */
	*result = json_object_new_array();
	if (*result == NULL)
		goto error;

	/* read the values */
	for (;;) {
		rc = unpacksingle(msg, &item);
		if (rc < 0)
			goto error;
		if (rc == 0)
			return 0;
		json_object_array_add(*result, item);
	}
error:
	json_object_put(*result);
	*result = NULL;
	return -1;
}

static int packsingle(struct sd_bus_message *msg, const char *signature, struct json_object *item)
{
	int index, count, rc, len;
	union any any;
	char *subsig;
	struct json_object_iterator it, end;

	len = lentype(signature, 0, 1);
	if (len < 0)
		goto error;

	switch (*signature) {
	case SD_BUS_TYPE_BOOLEAN:
		any.i32 = json_object_get_boolean(item);
		break;

	case SD_BUS_TYPE_BYTE:
		any.i32 = json_object_get_int(item);
		if (any.i32 != (int32_t)(uint8_t)any.i32)
			goto error;
		any.u8 = (uint8_t)any.i32;
		break;

	case SD_BUS_TYPE_INT16:
		any.i32 = json_object_get_int(item);
		if (any.i32 != (int32_t)(int16_t)any.i32)
			goto error;
		any.i16 = (int16_t)any.i32;
		break;

	case SD_BUS_TYPE_UINT16:
		any.i32 = json_object_get_int(item);
		if (any.i32 != (int32_t)(uint16_t)any.i32)
			goto error;
		any.u16 = (uint16_t)any.i32;
		break;

	case SD_BUS_TYPE_INT32:
		any.i64 = json_object_get_int64(item);
		if (any.i64 != (int64_t)(int32_t)any.i64)
			goto error;
		any.i32 = (int32_t)any.i64;
		break;

	case SD_BUS_TYPE_UINT32:
		any.i64 = json_object_get_int64(item);
		if (any.i64 != (int64_t)(uint32_t)any.i64)
			goto error;
		any.u32 = (uint32_t)any.i64;
		break;

	case SD_BUS_TYPE_INT64:
		any.i64 = json_object_get_int64(item);
		break;

	case SD_BUS_TYPE_UINT64:
		any.u64 = (uint64_t)json_object_get_int64(item);
		break;

	case SD_BUS_TYPE_DOUBLE:
		any.dbl = json_object_get_double(item);
		break;

	case SD_BUS_TYPE_STRING:
	case SD_BUS_TYPE_OBJECT_PATH:
	case SD_BUS_TYPE_SIGNATURE:
		any.cstr = json_object_get_string(item);
		break;

	case SD_BUS_TYPE_VARIANT:
		signature = signature_for_json(item);
		if (signature == NULL)
			goto error;
		rc = sd_bus_message_open_container(msg, SD_BUS_TYPE_VARIANT, signature);
		if (rc < 0)
			goto error;
		rc = packsingle(msg, signature, item);
		if (rc < 0)
			goto error;
		rc = sd_bus_message_close_container(msg);
		if (rc < 0)
			goto error;
		return len;

	case SD_BUS_TYPE_ARRAY:
		subsig = strndupa(signature + 1, len - 1);
		rc = sd_bus_message_open_container(msg, SD_BUS_TYPE_ARRAY, subsig);
		if (rc < 0)
			goto error;
		if (json_object_is_type(item, json_type_array)) {
			/* Is an array! */
			count = json_object_array_length(item);
			index = 0;
			while(index < count) {
				rc = packsingle(msg, subsig, json_object_array_get_idx(item, index++));
				if (rc < 0)
					goto error;
			}
		} else {
			/* Not an array! Check if it matches an string dictionnary */
			if (!json_object_is_type(item, json_type_object))
				goto error;
			if (*subsig++ != SD_BUS_TYPE_DICT_ENTRY_BEGIN)
				goto error;
			if (*subsig != SD_BUS_TYPE_STRING)
				goto error;
			/* iterate the object values */
			subsig[strlen(subsig) - 1] = 0;
			it = json_object_iter_begin(item);
			end = json_object_iter_end(item);
			while (!json_object_iter_equal(&it, &end)) {
				rc = sd_bus_message_open_container(msg, SD_BUS_TYPE_DICT_ENTRY, subsig);
				if (rc < 0)
					goto error;
				any.cstr = json_object_iter_peek_name(&it);
				rc = sd_bus_message_append_basic(msg, *subsig, &any);
				if (rc < 0)
					goto error;
				rc = packsingle(msg, subsig + 1, json_object_iter_peek_value(&it));
				if (rc < 0)
					goto error;
				rc = sd_bus_message_close_container(msg);
				if (rc < 0)
					goto error;
				json_object_iter_next(&it);
			}
		}
		rc = sd_bus_message_close_container(msg);
		if (rc < 0)
			goto error;
		return len;

	case SD_BUS_TYPE_STRUCT_BEGIN:
	case SD_BUS_TYPE_DICT_ENTRY_BEGIN:
		subsig = strndupa(signature + 1, len - 2);
		rc = sd_bus_message_open_container(msg,
			((*signature) == SD_BUS_TYPE_STRUCT_BEGIN) ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY,
			subsig);
		if (rc < 0)
			goto error;
		rc = packlist(msg, subsig, item);
		if (rc < 0)
			goto error;
		rc = sd_bus_message_close_container(msg);
		if (rc < 0)
			goto error;
		return len;

	default:
		goto error;
	}

	rc = sd_bus_message_append_basic(msg, *signature, &any);
	if (rc < 0)
		goto error;
	return len;

error:
	return -1;
}

static int packlist(struct sd_bus_message *msg, const char *signature, struct json_object *list)
{
	int rc, count, index, scan;
	struct json_object *item;

	scan = 0;
	if (list == NULL) {
		/* empty case */
		if (*signature)
			goto error;
		return scan;
	}

	if (!json_object_is_type(list, json_type_array)) {
		/* down grade gracefully to single */
		rc = packsingle(msg, signature, list);
		if (rc < 0)
			goto error;
		scan = rc;
		if (signature[scan] != 0)
			goto error;
		return scan;
	}

	/* iterate over elements */
	count = json_object_array_length(list);
	index = 0;
	for (;;) {
		/* check state */
		if (index == count && signature[scan] == 0)
			return scan;
		if (index == count || signature[scan] == 0)
			goto error;

		/* get the item */
		item = json_object_array_get_idx(list, index);
		if (item == NULL)
			goto error;

		/* pack the item */
		rc = packsingle(msg, signature + scan, item);
		if (rc < 0)
			goto error;

		/* advance */
		scan += rc;
		index++;
	}

error:
	return -(scan + 1);
}

/*
 * handle the reply
 */
static int on_rawcall_reply(sd_bus_message *msg, struct afb_req *req, sd_bus_error *ret_error)
{
	struct json_object *obj = NULL;
	int rc;
	const sd_bus_error *err;

	err = sd_bus_message_get_error(msg);
	if (err != NULL)
		afb_req_fail_f(*req, "failed", "DBus-error-name: %s, DBus-error-message: %s", err->name, err->message);
	else {
		rc = unpacklist(msg, &obj);
		if (rc < 0)
			afb_req_fail(*req, "failed", "can't unpack");
		else
			afb_req_success(*req, obj, NULL);
	}
	json_object_put(obj);
	afb_req_unref(*req);
	free(req);
	return 1;
}

/*
 * Make a raw call to DBUS method
 * The query should have:
 *   {
 *     "bus": "optional: 'system' or 'user' (default)"
 *     "destination": "destination handling the object",
 *     "path": "object path",
 *     "interface": "interface of the call",
 *     "member": "member of the interface of the call",
 *     "signature": "signature of the arguments",
 *     "arguments": "ARRAY of arguments"
 *   }
 */
static void rawcall(struct afb_req req)
{
	struct json_object *obj;
	struct json_object *args;

	const char *busname;
	const char *destination;
	const char *path;
	const char *interface;
	const char *member;
	const char *signature;

	struct sd_bus_message *msg = NULL;
	struct sd_bus *bus;
	int rc;

	/* get the query */
	obj = afb_req_json(req);
	if (obj == NULL)
		goto internal_error;

	/* get parameters */
	destination = strval(obj, "destination");
	path = strval(obj, "path");
	interface = strval(obj, "interface");
	member = strval(obj, "member");
	if (path == NULL || member == NULL)
		goto bad_request;

	/* get arguments */
	signature = strval(obj, "signature") ? : "";
	args = NULL;
	json_object_object_get_ex(obj, "arguments", &args);

	/* get bus */
	busname = strval(obj, "bus");
	if (busname != NULL && !strcmp(busname, "system"))
		bus = afb_daemon_get_system_bus(afbitf->daemon);
	else
		bus = afb_daemon_get_user_bus(afbitf->daemon);

	/* creates the message */
	rc = sd_bus_message_new_method_call(bus, &msg, destination, path, interface, member);
	if (rc != 0)
		goto internal_error;
	rc = packlist(msg, signature, args);
	if (rc < 0)
		goto bad_request;

	/*  */
	rc = sd_bus_call_async(bus, NULL, msg, (void*)on_rawcall_reply, afb_req_store(req), -1);
	if (rc < 0)
		goto internal_error;
	goto cleanup;

internal_error:
	afb_req_fail(req, "failed", "internal error");
	goto cleanup;

bad_request:
	afb_req_fail(req, "failed", "bad request");

cleanup:
	sd_bus_message_unref(msg);
}

/*
 * array of the verbs exported to afb-daemon
 */
static const struct afb_verb_desc_v1 binding_verbs[] = {
  /* VERB'S NAME       SESSION MANAGEMENT          FUNCTION TO CALL     SHORT DESCRIPTION */
  { .name= "rawcall",  .session= AFB_SESSION_NONE, .callback= rawcall,  .info= "raw call to dbus method" },
  { .name= NULL } /* marker for end of the array */
};

/*
 * description of the binding for afb-daemon
 */
static const struct afb_binding binding_description =
{
  /* description conforms to VERSION 1 */
  .type= AFB_BINDING_VERSION_1,
  .v1= {			/* fills the v1 field of the union when AFB_BINDING_VERSION_1 */
    .prefix= "dbus",		/* the API name (or binding name or prefix) */
    .info= "raw dbus binding",	/* short description of of the binding */
    .verbs = binding_verbs	/* the array describing the verbs of the API */
  }
};

/*
 * activation function for registering the binding called by afb-daemon
 */
const struct afb_binding *afbBindingV1Register(const struct afb_binding_interface *itf)
{
	afbitf = itf;			/* records the interface for accessing afb-daemon */
	return &binding_description;	/* returns the description of the binding */
}
stOfNameAndInst ) local included, excluded = {}, {} for i, v in ipairs( listOfNameAndInst ) do -- local name, instance = v[1], v[2] if patternFilter( patternIncFilter, v[1] ) then table.insert( included, v ) else table.insert( excluded, v ) end end return included, excluded end function M.LuaUnit:runSuiteByInstances( listOfNameAndInst ) --[[ Run an explicit list of tests. Each item of the list must be one of: * { function name, function instance } * { class name, class instance } * { class.method name, class instance } ]] local expandedList = self.expandClasses( listOfNameAndInst ) if self.shuffle then randomizeTable( expandedList ) end local filteredList, filteredOutList = self.applyPatternFilter( self.patternIncludeFilter, expandedList ) self:startSuite( #filteredList, #filteredOutList ) for i,v in ipairs( filteredList ) do local name, instance = v[1], v[2] if M.LuaUnit.asFunction(instance) then self:execOneFunction( nil, name, nil, instance ) else -- expandClasses() should have already taken care of sanitizing the input assert( type(instance) == 'table' ) local className, methodName = M.LuaUnit.splitClassMethod( name ) assert( className ~= nil ) local methodInstance = instance[methodName] assert(methodInstance ~= nil) self:execOneFunction( className, methodName, instance, methodInstance ) end if self.result.aborted then break -- "--error" or "--failure" option triggered end end if self.lastClassName ~= nil then self:endClass() end self:endSuite() if self.result.aborted then print("LuaUnit ABORTED (as requested by --error or --failure option)") os.exit(-2) end end function M.LuaUnit:runSuiteByNames( listOfName ) --[[ Run LuaUnit with a list of generic names, coming either from command-line or from global namespace analysis. Convert the list into a list of (name, valid instances (table or function)) and calls runSuiteByInstances. ]] local instanceName, instance local listOfNameAndInst = {} for i,name in ipairs( listOfName ) do local className, methodName = M.LuaUnit.splitClassMethod( name ) if className then instanceName = className instance = _G[instanceName] if instance == nil then error( "No such name in global space: "..instanceName ) end if type(instance) ~= 'table' then error( 'Instance of '..instanceName..' must be a table, not '..type(instance)) end local methodInstance = instance[methodName] if methodInstance == nil then error( "Could not find method in class "..tostring(className).." for method "..tostring(methodName) ) end else -- for functions and classes instanceName = name instance = _G[instanceName] end if instance == nil then error( "No such name in global space: "..instanceName ) end if (type(instance) ~= 'table' and type(instance) ~= 'function') then error( 'Name must match a function or a table: '..instanceName ) end table.insert( listOfNameAndInst, { name, instance } ) end self:runSuiteByInstances( listOfNameAndInst ) end function M.LuaUnit.run(...) -- Run some specific test classes. -- If no arguments are passed, run the class names specified on the -- command line. If no class name is specified on the command line -- run all classes whose name starts with 'Test' -- -- If arguments are passed, they must be strings of the class names -- that you want to run or generic command line arguments (-o, -p, -v, ...) local runner = M.LuaUnit.new() return runner:runSuite(...) end function M.LuaUnit:runSuite( ... ) local args = {...} if type(args[1]) == 'table' and args[1].__class__ == 'LuaUnit' then -- run was called with the syntax M.LuaUnit:runSuite() -- we support both M.LuaUnit.run() and M.LuaUnit:run() -- strip out the first argument table.remove(args,1) end if #args == 0 then args = cmdline_argv end local options = pcall_or_abort( M.LuaUnit.parseCmdLine, args ) -- We expect these option fields to be either `nil` or contain -- valid values, so it's safe to always copy them directly. self.verbosity = options.verbosity self.quitOnError = options.quitOnError self.quitOnFailure = options.quitOnFailure self.fname = options.fname self.exeRepeat = options.exeRepeat self.patternIncludeFilter = options.pattern self.shuffle = options.shuffle if options.output then if options.output:lower() == 'junit' and options.fname == nil then print('With junit output, a filename must be supplied with -n or --name') os.exit(-1) end pcall_or_abort(self.setOutputType, self, options.output) end self:runSuiteByNames( options.testNames or M.LuaUnit.collectTests() ) return self.result.notSuccessCount end -- class LuaUnit -- For compatbility with LuaUnit v2 M.run = M.LuaUnit.run M.Run = M.LuaUnit.run function M:setVerbosity( verbosity ) M.LuaUnit.verbosity = verbosity end M.set_verbosity = M.setVerbosity M.SetVerbosity = M.setVerbosity return M