memleak

#!/usr/bin/env bcc-lua
--[[
]]
local bpf_source = [[
#include <uapi/linux/ptrace.h>
struct alloc_info_t {
        u64 size;
        u64 timestamp_ns;
        int stack_id;
};
BPF_HASH(sizes, u64);
BPF_HASH(allocs, u64, struct alloc_info_t);
BPF_STACK_TRACE(stack_traces, 10240);
int alloc_enter(struct pt_regs *ctx, size_t size)
{
        SIZE_FILTER
        if (SAMPLE_EVERY_N > 1) {
                u64 ts = bpf_ktime_get_ns();
                if (ts % SAMPLE_EVERY_N != 0)
                        return 0;
        }
        u64 pid = bpf_get_current_pid_tgid();
        u64 size64 = size;
        sizes.update(&pid, &size64);
        if (SHOULD_PRINT)
                bpf_trace_printk("alloc entered, size = %u\n", size);
        return 0;
}
int alloc_exit(struct pt_regs *ctx)
{
        u64 address = PT_REGS_RC(ctx);
        u64 pid = bpf_get_current_pid_tgid();
        u64* size64 = sizes.lookup(&pid);
        struct alloc_info_t info = {0};
        if (size64 == 0)
                return 0; // missed alloc entry
        info.size = *size64;
        sizes.delete(&pid);
        info.timestamp_ns = bpf_ktime_get_ns();
        info.stack_id = stack_traces.get_stackid(ctx, STACK_FLAGS);
        allocs.update(&address, &info);
        if (SHOULD_PRINT) {
                bpf_trace_printk("alloc exited, size = %lu, result = %lx\n",
                                 info.size, address);
        }
        return 0;
}
int free_enter(struct pt_regs *ctx, void *address)
{
        u64 addr = (u64)address;
        struct alloc_info_t *info = allocs.lookup(&addr);
        if (info == 0)
                return 0;
        allocs.delete(&addr);
        if (SHOULD_PRINT) {
                bpf_trace_printk("free entered, address = %lx, size = %lu\n",
                                 address, info->size);
        }
        return 0;
}
]]
return function(BPF, utils)
  local parser = utils.argparse("memleak", "Catch memory leaks")
  parser:flag("-t --trace")
  parser:flag("-a --show-allocs")
  parser:option("-p --pid"):convert(tonumber)
  parser:option("-i --interval", "", 5):convert(tonumber)
  parser:option("-o --older", "", 500):convert(tonumber)
  parser:option("-s --sample-rate", "", 1):convert(tonumber)
  parser:option("-z --min-size", ""):convert(tonumber)
  parser:option("-Z --max-size", ""):convert(tonumber)
  parser:option("-T --top", "", 10):convert(tonumber)
  local args = parser:parse()
  local size_filter = ""
  if args.min_size and args.max_size then
    size_filter = "if (size < %d || size > %d) return 0;" %  {args.min_size, args.max_size}
  elseif args.min_size then
    size_filter = "if (size < %d) return 0;" % args.min_size
  elseif args.max_size then
    size_filter = "if (size > %d) return 0;" % args.max_size
  end
  local stack_flags = "0"
  if args.pid then
    stack_flags = stack_flags .. "|BPF_F_USER_STACK"
  end
  local text = bpf_source
  text = text:gsub("SIZE_FILTER", size_filter)
  text = text:gsub("STACK_FLAGS",  stack_flags)
  text = text:gsub("SHOULD_PRINT", args.trace and "1" or "0")
  text = text:gsub("SAMPLE_EVERY_N", tostring(args.sample_rate))
  local bpf = BPF:new{text=text, debug=0}
  local syms = nil
  local min_age_ns = args.older * 1e6
  if args.pid then
    print("Attaching to malloc and free in pid %d, Ctrl+C to quit." % args.pid)
    bpf:attach_uprobe{name="c", sym="malloc", fn_name="alloc_enter", pid=args.pid}
    bpf:attach_uprobe{name="c", sym="malloc", fn_name="alloc_exit", pid=args.pid, retprobe=true}
    bpf:attach_uprobe{name="c", sym="free", fn_name="free_enter", pid=args.pid}
  else
    print("Attaching to kmalloc and kfree, Ctrl+C to quit.")
    bpf:attach_kprobe{event="__kmalloc", fn_name="alloc_enter"}
    bpf:attach_kprobe{event="__kmalloc", fn_name="alloc_exit", retprobe=true} -- TODO
    bpf:attach_kprobe{event="kfree", fn_name="free_enter"}
  end
  local syms = BPF.SymbolCache(args.pid)
  local allocs = bpf:get_table("allocs")
  local stack_traces = bpf:get_table("stack_traces")
  local function resolve(addr)
    local sym = syms:resolve(addr)
    if args.pid == nil then
      sym = sym .. " [kernel]"
    end
    return string.format("%s (%p)", sym, addr)
  end
  local function print_outstanding()
    local alloc_info = {}
    local now = utils.posix.time_ns()
    print("[%s] Top %d stacks with outstanding allocations:" %
      {os.date("%H:%M:%S"), args.top})
    for address, info in allocs:items() do
      if now - min_age_ns >= tonumber(info.timestamp_ns) then
        local stack_id = tonumber(info.stack_id)
        if stack_id >= 0 then
          if alloc_info[stack_id] then
            local s = alloc_info[stack_id]
            s.count = s.count + 1
            s.size = s.size + tonumber(info.size)
          else
            local stack = stack_traces:get(stack_id, resolve)
            alloc_info[stack_id] = { stack=stack, count=1, size=tonumber(info.size) }
          end
        end
        if args.show_allocs then
          print("\taddr = %p size = %s" % {address, tonumber(info.size)})
        end
      end
    end
    local top = table.values(alloc_info)
    table.sort(top, function(a, b) return a.size > b.size end)
    for n, alloc in ipairs(top) do
      print("\t%d bytes in %d allocations from stack\n\t\t%s" %
        {alloc.size, alloc.count, table.concat(alloc.stack, "\n\t\t")})
      if n == args.top then break end
    end
  end
  if args.trace then
    local pipe = bpf:pipe()
    while true do
      print(pipe:trace_fields())
    end
  else
    while true do
      utils.posix.sleep(args.interval)
      syms:refresh()
      print_outstanding()
    end
  end
end


sock-parse-dns

#!/usr/bin/env bcc-lua
--[[
]]
-- Simple parsing example of UDP/DNS that counts frequency of QTYPEs.
-- It shows how to parse packet variable-length packet structures.
local ffi = require("ffi")
local bpf = require("bpf")
local S = require("syscall")
-- Shared part of the program
local map = assert(bpf.map('array', 256))
-- Kernel-space part of the program
local prog = bpf.socket('lo', function (skb)
	local ip = pkt.ip   -- Accept only UDP messages
	if ip.proto ~= c.ip.proto_udp then return false end
	local udp = ip.udp  -- Only messages >12 octets (DNS header)
	if udp.length < 12 then return false end
	-- Unroll QNAME (up to 2 labels)
	udp = udp.data + 12
	local label = udp[0]
	if label > 0 then
		udp = udp + label + 1
		label = udp[0]
		if label > 0 then
			udp = udp + label + 1
		end
	end
	-- Track QTYPE (low types)
	if udp[0] == 0 then
		local qtype = udp[2] -- Low octet from QTYPE
		xadd(map[qtype], 1)
	end
end)
-- User-space part of the program
for _ = 1, 10 do
	for k,v in map.pairs,map,0 do
		v = tonumber(v)
		if v > 0 then
			print(string.format('TYPE%d: %d', k, v))
		end
	end
	S.sleep(1)
end


uprobe-readline-perf

#!/usr/bin/env bcc-lua
--[[
]]
-- Trace readline() call from all bash instances (print bash commands from all running shells).
-- This is rough equivallent to `bashreadline` with output through perf event API.
-- Source: http://www.brendangregg.com/blog/2016-02-08/linux-ebpf-bcc-uprobes.html
local ffi = require('ffi')
local bpf = require('bpf')
local S = require('syscall')
-- Perf event map
local sample_t = 'struct { uint64_t pid; char str[80]; }'
local events = bpf.map('perf_event_array')
-- Kernel-space part of the program
local probe = bpf.uprobe('/bin/bash:readline', function (ptregs)
	local sample = ffi.new(sample_t)
	sample.pid = pid_tgid()
	ffi.copy(sample.str, ffi.cast('char *', ptregs.ax)) -- Cast `ax` to string pointer and copy to buffer
	perf_submit(events, sample)                         -- Write buffer to perf event map
end, true, -1, 0)
-- User-space part of the program
local log = events:reader(nil, 0, sample_t) -- Must specify PID or CPU_ID to observe
print('            TASK-PID         TIMESTAMP  FUNCTION')
print('               | |               |         |')
while true do
	log:block()               -- Wait until event reader is readable
	for _,e in log:read() do  -- Collect available reader events
		print(string.format('%12s%-16s %-10s %s', '', tonumber(e.pid), os.date("%H:%M:%S"), ffi.string(e.str)))
	end
end


#!/usr/bin/env bcc-lua
--[[
]]
assert(arg[1], "usage: strlen_count PID")
local program = string.gsub([[
#include <uapi/linux/ptrace.h>
int printarg(struct pt_regs *ctx) {
  if (!PT_REGS_PARM1(ctx))
    return 0;
  u32 pid = bpf_get_current_pid_tgid();
  if (pid != PID)
    return 0;
  char str[128] = {};
  bpf_probe_read_user(&str, sizeof(str), (void *)PT_REGS_PARM1(ctx));
  bpf_trace_printk("strlen(\"%s\")\n", &str);
  return 0;
};
]], "PID", arg[1])
return function(BPF)
  local b = BPF:new{text=program, debug=0}
  b:attach_uprobe{name="c", sym="strlen", fn_name="printarg"}
  local pipe = b:pipe()
  while true do
    local task, pid, cpu, flags, ts, msg = pipe:trace_fields()
    print("%-18.9f %-16s %-6d %s" % {ts, task, pid, msg})
  end
end


function setup_path()
  local str = require("debug").getinfo(2, "S").source:sub(2)
  local cwd = str:match("(.*/)")
  local bpf_path = cwd.."/../../src/lua/?.lua;"
  local test_path = cwd.."/?.lua;"
  package.path = bpf_path..test_path..package.path
end

setup_path()

USE_EXPECTED_ACTUAL_IN_ASSERT_EQUALS = false
EXPORT_ASSERT_TO_GLOBALS = true
require("luaunit")

rawset(_G, "BCC", require("bcc.init"))
rawset(_G, "BPF", BCC.BPF)

log.enabled = false

return function (name, f)
  rawset(_G, name, f)
  os.exit(LuaUnit.run())
end


luaunit

--[[
        luaunit.lua
Description: A unit testing framework
Homepage: https://github.com/bluebird75/luaunit
Version: 3.2
]]--
require("math")
local M={}
-- private exported functions (for testing)
M.private = {}
M.VERSION='3.2'
--[[ 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
-- 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
-- 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 overridden
local cmdline_argv = rawget(_G, "arg")
M.FAILURE_PREFIX = 'LuaUnit test FAILURE: ' -- prefix string for failed tests
M.USAGE=[[Usage: lua <your_test_suite.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
  -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
  -p, --pattern PATTERN:  Execute all test names matching the Lua PATTERN
                          May be repeated to include severals patterns
                          Make sure you escape magic chars like +? with %
  testname1, testname2, ... : tests to run in the form of testFunction,
                              TestClass or TestClass.testMethod
]]
----------------------------------------------------------------
--
--                 general utility functions
--
----------------------------------------------------------------
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.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 count = #state.sortedIdx
        local lower, upper = 1, count
        repeat
            state.lastIdx = math.modf((lower + upper) / 2)
            key = state.sortedIdx[state.lastIdx]
            if key == control then break; end -- key found (and thus prev index)
            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 = count -- ... so ensure no match for the 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
local function strsplit(delimiter, text)
-- Split text into a list consisting of the strings in text,
-- separated by strings matching delimiter (which may be a pattern).
-- example: strsplit(",%s*", "Anna, Bob, Charlie,Dolores")
    if string.find("", delimiter, 1, true) then -- this would result in endless loops
        error("delimiter matches empty string!")
    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 .. table.concat(strsplit('\n', s), '\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 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:
    <<stack traceback:
        example_with_luaunit.lua:130: in function 'test2_withFailure'
        ./luaunit.lua:1449: in function <./luaunit.lua:1449>
        [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:
    <<stack traceback:
        ./luaunit.lua:545: in function 'assertEquals'
        example_with_luaunit.lua:58: in function 'TestToto.test7'
        ./luaunit.lua:1517: in function <./luaunit.lua:1517>
        [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 ?>>
    <<stack traceback:
        luaunit2/example_with_luaunit.lua:124: in function 'test1_withFailure'
        luaunit2/luaunit.lua:1532: in function <luaunit2/luaunit.lua:1532>
        [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, keeponeline, printTableRefs, recursionTable )
    local type_v = type(v)
    if "string" == type_v  then
        if keeponeline then v = v:gsub("\n", "\\n") end
        -- 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)
    end
    return tostring(v)
end
local function prettystr( v, keeponeline )
    --[[ Better string conversion, to display nice variable content:
    For strings, if keeponeline is set to true, string is displayed on one line, with visible \n
    * string are enclosed with " by default, or with ' if string contains a "
    * if table is a class, display class name
    * tables are expanded
    ]]--
    local recursionTable = {}
    local s = prettystr_sub(v, 1, keeponeline, 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, keeponeline, true, recursionTable)
    end
    return s
end
M.prettystr = prettystr
local function prettystrPadded(value1, value2, suffix_a, suffix_b)
    --[[
    This function helps with the recurring task of constructing the "expected
    vs. actual" error messages. It takes two arbitrary values and formats
    corresponding strings with prettystr().
    To keep the (possibly complex) output more readable in case the resulting
    strings contain line breaks, they get automatically prefixed with additional
    newlines. Both suffixes are optional (default to empty strings), and get
    appended to the "value1" string. "suffix_a" is used if line breaks were
    encountered, "suffix_b" otherwise.
    Returns the two formatted strings (including padding/newlines).
    ]]
    local str1, str2 = prettystr(value1), prettystr(value2)
    if hasNewLine(str1) or hasNewLine(str2) then
        -- line break(s) detected, add padding
        return "\n" .. str1 .. (suffix_a or ""), "\n" .. str2
    end
    return str1 .. (suffix_b or ""), str2
end
M.private.prettystrPadded = prettystrPadded
local function _table_keytostring(k)
    -- like prettystr but do not enclose with "" if the string is just alphanumerical
    -- this is better for displaying table keys who are often simple strings
    if "string" == type(k) and k:match("^[_%a][_%w]*$") then
        return k
    end
    return prettystr(k)
end
M.private._table_keytostring = _table_keytostring
local TABLE_TOSTRING_SEP = ", "
local TABLE_TOSTRING_SEP_LEN = string.len(TABLE_TOSTRING_SEP)
local function _table_tostring( tbl, indentLevel, printTableRefs, recursionTable )
    printTableRefs = printTableRefs or M.PRINT_TABLE_REF_IN_ERROR_MSG
    recursionTable = recursionTable or {}
    recursionTable[tbl] = true
    local result, dispOnMultLines = {}, false
    local entry, count, seq_index = nil, 0, 1
    for k, v in sortedPairs( tbl ) do
        if k == seq_index then
            -- for the sequential part of tables, we'll skip the "<key>=" output
            entry = ''
            seq_index = seq_index + 1
        else
            entry = _table_keytostring( k ) .. "="
        end
        if recursionTable[v] then -- recursion detected!
            recursionTable.recursionDetected = true
            entry = entry .. "<"..tostring(v)..">"
        else
            entry = entry ..
                prettystr_sub( v, indentLevel+1, true, printTableRefs, recursionTable )
        end
        count = count + 1
        result[count] = entry
    end
    -- set dispOnMultLines if the maximum LINE_LENGTH would be exceeded
    local totalLength = 0
    for k, v in ipairs( result ) do
        totalLength = totalLength + string.len( v )
        if totalLength >= M.LINE_LENGTH then
            dispOnMultLines = true
            break
        end
    end
    if not dispOnMultLines then
        -- adjust with length of separator(s):
        -- two items need 1 sep, three items two seps, ... plus len of '{}'
        if count > 0 then
            totalLength = totalLength + TABLE_TOSTRING_SEP_LEN * (count - 1)
        end
        dispOnMultLines = totalLength + 2 >= M.LINE_LENGTH
    end
    -- now reformat the result table (currently holding element strings)
    if dispOnMultLines then
        local indentString = string.rep("    ", indentLevel - 1)
        result = {"{\n    ", indentString,
                  table.concat(result, ",\n    " .. indentString), "\n",
                  indentString, "}"}
    else
        result = {"{", table.concat(result, TABLE_TOSTRING_SEP), "}"}
    end
    if printTableRefs then
        table.insert(result, 1, "<"..tostring(tbl).."> ") -- prepend table ref
    end
    return table.concat(result)
end
M.private._table_tostring = _table_tostring -- prettystr_sub() needs it
local function _table_contains(t, element)
    if t then
        for _, value in pairs(t) do
            if type(value) == type(element) then
                if type(element) == 'table' then
                    -- if we wanted recursive items content comparison, we could use
                    -- _is_table_items_equals(v, expected) but one level of just comparing
                    -- items is sufficient
                    if M.private._is_table_equals( value, element ) then
                        return true
                    end
                else
                    if value == element then
                        return true
                    end
                end
            end
        end
    end
    return false
end
local function _is_table_items_equals(actual, expected )
    if (type(actual) == 'table') and (type(expected) == 'table') then
        for k,v in pairs(actual) do
            if not _table_contains(expected, v) then
                return false
            end
        end
        for k,v in pairs(expected) do
            if not _table_contains(actual, v) then
                return false
            end
        end
        return true
    elseif type(actual) ~= type(expected) then
        return false
    elseif actual == expected then
        return true
    end
    return false
end
local function _is_table_equals(actual, expected)
    if (type(actual) == 'table') and (type(expected) == 'table') then
        if (#actual ~= #expected) then
            return false
        end
        local actualTableKeys = {}
        for k,v in pairs(actual) do
            if M.TABLE_EQUALS_KEYBYCONTENT and type(k) == "table" then
                -- If the keys are tables, things get a bit tricky here as we
                -- can have _is_table_equals(k1, k2) and t[k1] ~= t[k2]. So we
                -- collect actual's table keys, group them by length for
                -- performance, and then for each table key in expected we look
                -- it up in actualTableKeys.
                if not actualTableKeys[#k] then actualTableKeys[#k] = {} end
                table.insert(actualTableKeys[#k], k)
            else
                if not _is_table_equals(v, expected[k]) then
                    return false
                end
            end
        end
        for k,v in pairs(expected) do
            if M.TABLE_EQUALS_KEYBYCONTENT and type(k) == "table" then
                local candidates = actualTableKeys[#k]
                if not candidates then return false end
                local found
                for i, candidate in pairs(candidates) do
                    if _is_table_equals(candidate, k) then
                        found = candidate
                        -- Remove the candidate we matched against from the list
                        -- of candidates, so each key in actual can only match
                        -- one key in expected.
                        candidates[i] = nil
                        break
                    end
                end
                if not(found and _is_table_equals(actual[found], v)) then return false end
            else
                if not _is_table_equals(v, actual[k]) then
                    return false
                end
            end
        end
        if M.TABLE_EQUALS_KEYBYCONTENT then
            for _, keys in pairs(actualTableKeys) do
                -- if there are any keys left in any actualTableKeys[i] then
                -- that is a key in actual with no matching key in expected,
                -- and so the tables aren't equal.
                if next(keys) then return false end
            end
        end
        return true
    elseif type(actual) ~= type(expected) then
        return false
    elseif actual == expected then
        return true
    end
    return false
end
M.private._is_table_equals = _is_table_equals
local function failure(msg, level)
    -- raise an error indicating a test failure
    -- for error() compatibility we adjust "level" here (by +1), to report the
    -- calling context
    error(M.FAILURE_PREFIX .. msg, (level or 1) + 1)
end
local function fail_fmt(level, ...)
     -- failure with printf-style formatted message and given error level
    failure(string.format(...), (level or 1) + 1)
end
M.private.fail_fmt = fail_fmt
local function error_fmt(level, ...)
     -- printf-style error()
    error(string.format(...), (level or 1) + 1)
end
----------------------------------------------------------------
--
--                     assertions
--
----------------------------------------------------------------
local function errorMsgEquality(actual, expected)
    if not M.ORDER_ACTUAL_EXPECTED then
        expected, actual = actual, expected
    end
    if type(expected) == 'string' or type(expected) == 'table' then
        expected, actual = prettystrPadded(expected, actual)
        return string.format("expected: %s\nactual: %s", expected, actual)
    end
    return string.format("expected: %s, actual: %s",
                         prettystr(expected), prettystr(actual))
end
function M.assertError(f, ...)
    -- assert that calling f with the arguments will raise an error
    -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error
    if pcall( f, ... ) then
        failure( "Expected an error when calling function but no error generated", 2 )
    end
end
function M.assertTrue(value)
    if not value then
        failure("expected: true, actual: " ..prettystr(value), 2)
    end
end
function M.assertFalse(value)
    if value then
        failure("expected: false, actual: " ..prettystr(value), 2)
    end
end
function M.assertIsNil(value)
    if value ~= nil then
        failure("expected: nil, actual: " ..prettystr(value), 2)
    end
end
function M.assertNotIsNil(value)
    if value == nil then
        failure("expected non nil value, received nil", 2)
    end
end
function M.assertEquals(actual, expected)
    if type(actual) == 'table' and type(expected) == 'table' then
        if not _is_table_equals(actual, expected) then
            failure( errorMsgEquality(actual, expected), 2 )
        end
    elseif type(actual) ~= type(expected) then
        failure( errorMsgEquality(actual, expected), 2 )
    elseif actual ~= expected then
        failure( errorMsgEquality(actual, expected), 2 )
    end
end
-- Help Lua in corner cases like almostEquals(1.1, 1.0, 0.1), which by default
-- may not work. We need to give margin a small boost; EPSILON defines the
-- default value to use for this:
local EPSILON = 0.00000000001
function M.almostEquals( actual, expected, margin, margin_boost )
    if type(actual) ~= 'number' or type(expected) ~= 'number' or type(margin) ~= 'number' then
        error_fmt(3, 'almostEquals: must supply only number arguments.\nArguments supplied: %s, %s, %s',
            prettystr(actual), prettystr(expected), prettystr(margin))
    end
    if margin <= 0 then
        error('almostEquals: margin must be positive, current value is ' .. margin, 3)
    end
    local realmargin = margin + (margin_boost or EPSILON)
    return math.abs(expected - actual) <= realmargin
end
function M.assertAlmostEquals( actual, expected, margin )
    -- check that two floats are close by margin
    if not M.almostEquals(actual, expected, margin) then
        if not M.ORDER_ACTUAL_EXPECTED then
            expected, actual = actual, expected
        end
        fail_fmt(2, 'Values are not almost equal\nExpected: %s with margin of %s, received: %s',
                 expected, margin, actual)
    end
end
function M.assertNotEquals(actual, expected)
    if type(actual) ~= type(expected) then
        return
    end
    if type(actual) == 'table' and type(expected) == 'table' then
        if not _is_table_equals(actual, expected) then
            return
        end
    elseif actual ~= expected then
        return
    end
    fail_fmt(2, 'Received the not expected value: %s', prettystr(actual))
end
function M.assertNotAlmostEquals( actual, expected, margin )
    -- check that two floats are not close by margin
    if M.almostEquals(actual, expected, margin) then
        if not M.ORDER_ACTUAL_EXPECTED then
            expected, actual = actual, expected
        end
        fail_fmt(2, 'Values are almost equal\nExpected: %s with a difference above margin of %s, received: %s',
                 expected, margin, actual)
    end
end
function M.assertStrContains( str, sub, useRe )
    -- this relies on lua string.find function
    -- a string always contains the empty string
    if not string.find(str, sub, 1, not useRe) then
        sub, str = prettystrPadded(sub, str, '\n')
        fail_fmt(2, 'Error, %s %s was not found in string %s',
                 useRe and 'regexp' or 'substring', sub, str)
    end
end
function M.assertStrIContains( str, sub )
    -- this relies on lua string.find function
    -- a string always contains the empty string
    if not string.find(str:lower(), sub:lower(), 1, true) then
        sub, str = prettystrPadded(sub, str, '\n')
        fail_fmt(2, 'Error, substring %s was not found (case insensitively) in string %s',
                 sub, str)
    end
end
function M.assertNotStrContains( str, sub, useRe )
    -- this relies on lua string.find function
    -- a string always contains the empty string
    if string.find(str, sub, 1, not useRe) then
        sub, str = prettystrPadded(sub, str, '\n')
        fail_fmt(2, 'Error, %s %s was found in string %s',
                 useRe and 'regexp' or 'substring', sub, str)
    end
end
function M.assertNotStrIContains( str, sub )
    -- this relies on lua string.find function
    -- a string always contains the empty string
    if string.find(str:lower(), sub:lower(), 1, true) then
        sub, str = prettystrPadded(sub, str, '\n')
        fail_fmt(2, 'Error, substring %s was found (case insensitively) in string %s',
                 sub, str)
    end
end
function M.assertStrMatches( str, pattern, start, final )
    -- Verify a full match for the string
    -- for a partial match, simply use assertStrContains with useRe set to true
    if not strMatch( str, pattern, start, final ) then
        pattern, str = prettystrPadded(pattern, str, '\n')
        fail_fmt(2, 'Error, pattern %s was not matched by string %s',
                 pattern, str)
    end
end
function M.assertErrorMsgEquals( expectedMsg, func, ... )
    -- assert that calling f with the arguments will raise an error
    -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error
    local no_error, error_msg = pcall( func, ... )
    if no_error then
        failure( 'No error generated when calling function but expected error: "'..expectedMsg..'"', 2 )
    end
    if error_msg ~= expectedMsg then
        error_msg, expectedMsg = prettystrPadded(error_msg, expectedMsg)
        fail_fmt(2, 'Exact error message expected: %s\nError message received: %s\n',
                 expectedMsg, error_msg)
    end
end
function M.assertErrorMsgContains( partialMsg, func, ... )
    -- assert that calling f with the arguments will raise an error
    -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error
    local no_error, error_msg = pcall( func, ... )
    if no_error then
        failure( 'No error generated when calling function but expected error containing: '..prettystr(partialMsg), 2 )
    end
    if not string.find( error_msg, partialMsg, nil, true ) then
        error_msg, partialMsg = prettystrPadded(error_msg, partialMsg)
        fail_fmt(2, 'Error message does not contain: %s\nError message received: %s\n',
                 partialMsg, error_msg)
    end
end
function M.assertErrorMsgMatches( expectedMsg, func, ... )
    -- assert that calling f with the arguments will raise an error
    -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error
    local no_error, error_msg = pcall( func, ... )
    if no_error then
        failure( 'No error generated when calling function but expected error matching: "'..expectedMsg..'"', 2 )
    end
    if not strMatch( error_msg, expectedMsg ) then
        expectedMsg, error_msg = prettystrPadded(expectedMsg, error_msg)
        fail_fmt(2, 'Error message does not match: %s\nError message received: %s\n',
                 expectedMsg, error_msg)
    end
end
--[[
Add type assertion functions to the module table M. Each of these functions
takes a single parameter "value", and checks that its Lua type matches the
expected string (derived from the function name):
M.assertIsXxx(value) -> ensure that type(value) conforms to "xxx"
]]
for _, funcName in ipairs(
    {'assertIsNumber', 'assertIsString', 'assertIsTable', 'assertIsBoolean',
     'assertIsFunction', 'assertIsUserdata', 'assertIsThread'}
) do
    local typeExpected = funcName:match("^assertIs([A-Z]%a*)$")
    -- Lua type() always returns lowercase, also make sure the match() succeeded
    typeExpected = typeExpected and typeExpected:lower()
                   or error("bad function name '"..funcName.."' for type assertion")
    M[funcName] = function(value)
        if type(value) ~= typeExpected then
            fail_fmt(2, 'Expected: a %s value, actual: type %s, value %s',
                     typeExpected, type(value), prettystrPadded(value))
        end
    end
end
--[[
Add non-type assertion functions to the module table M. Each of these functions
takes a single parameter "value", and checks that its Lua type differs from the
expected string (derived from the function name):
M.assertNotIsXxx(value) -> ensure that type(value) is not "xxx"
]]
for _, funcName in ipairs(
    {'assertNotIsNumber', 'assertNotIsString', 'assertNotIsTable', 'assertNotIsBoolean',
     'assertNotIsFunction', 'assertNotIsUserdata', 'assertNotIsThread'}
) do
    local typeUnexpected = funcName:match("^assertNotIs([A-Z]%a*)$")
    -- Lua type() always returns lowercase, also make sure the match() succeeded
    typeUnexpected = typeUnexpected and typeUnexpected:lower()
                   or error("bad function name '"..funcName.."' for type assertion")
    M[funcName] = function(value)
        if type(value) == typeUnexpected then
            fail_fmt(2, 'Not expected: a %s type, actual: value %s',
                     typeUnexpected, prettystrPadded(value))
        end
    end
end
function M.assertIs(actual, expected)
    if actual ~= expected then
        if not M.ORDER_ACTUAL_EXPECTED then
            actual, expected = expected, actual
        end
        expected, actual = prettystrPadded(expected, actual, '\n', ', ')
        fail_fmt(2, 'Expected object and actual object are not the same\nExpected: %sactual: %s',
                 expected, actual)
    end
end
function M.assertNotIs(actual, expected)
    if actual == expected then
        if not M.ORDER_ACTUAL_EXPECTED then
            expected = actual
        end
        fail_fmt(2, 'Expected object and actual object are the same object: %s',
                 prettystrPadded(expected))
    end
end
function M.assertItemsEquals(actual, expected)
    -- checks that the items of table expected
    -- are contained in table actual. Warning, this function
    -- is at least O(n^2)
    if not _is_table_items_equals(actual, expected ) then
        expected, actual = prettystrPadded(expected, actual)
        fail_fmt(2, 'Contents of the tables are not identical:\nExpected: %s\nActual: %s',
                 expected, actual)
    end
end
----------------------------------------------------------------
--                     Compatibility layer
----------------------------------------------------------------
-- for compatibility with LuaUnit v2.x
function M.wrapFunctions(...)
    io.stderr:write( [[Use of WrapFunction() is no longer needed.
Just prefix your test function names with "test" or "Test" and they
will be picked up and run by LuaUnit.]] )
    -- In LuaUnit version <= 2.1 , this function was necessary to include
    -- a test function inside the global test suite. Nowadays, the functions
    -- are simply run directly as part of the test discovery process.
    -- so just do nothing !
    --[[
    local testClass, testFunction
    testClass = {}
    local function storeAsMethod(idx, testName)
        testFunction = _G[testName]
        testClass[testName] = testFunction
    end
    for i,v in ipairs({...}) do
        storeAsMethod( i, v )
    end
    return testClass
    ]]
end
local list_of_funcs = {
    -- { official function name , alias }
    -- general assertions
    { 'assertEquals'            , 'assert_equals' },
    { 'assertItemsEquals'       , 'assert_items_equals' },
    { 'assertNotEquals'         , 'assert_not_equals' },
    { 'assertAlmostEquals'      , 'assert_almost_equals' },
    { 'assertNotAlmostEquals'   , 'assert_not_almost_equals' },
    { 'assertTrue'              , 'assert_true' },
    { 'assertFalse'             , 'assert_false' },
    { 'assertStrContains'       , 'assert_str_contains' },
    { 'assertStrIContains'      , 'assert_str_icontains' },
    { 'assertNotStrContains'    , 'assert_not_str_contains' },
    { 'assertNotStrIContains'   , 'assert_not_str_icontains' },
    { 'assertStrMatches'        , 'assert_str_matches' },
    { 'assertError'             , 'assert_error' },
    { 'assertErrorMsgEquals'    , 'assert_error_msg_equals' },
    { 'assertErrorMsgContains'  , 'assert_error_msg_contains' },
    { 'assertErrorMsgMatches'   , 'assert_error_msg_matches' },
    { 'assertIs'                , 'assert_is' },
    { 'assertNotIs'             , 'assert_not_is' },
    { 'wrapFunctions'           , 'WrapFunctions' },
    { 'wrapFunctions'           , 'wrap_functions' },
    -- type assertions: assertIsXXX -> assert_is_xxx
    { 'assertIsNumber'          , 'assert_is_number' },
    { 'assertIsString'          , 'assert_is_string' },
    { 'assertIsTable'           , 'assert_is_table' },
    { 'assertIsBoolean'         , 'assert_is_boolean' },
    { 'assertIsNil'             , 'assert_is_nil' },
    { 'assertIsFunction'        , 'assert_is_function' },
    { 'assertIsThread'          , 'assert_is_thread' },
    { 'assertIsUserdata'        , 'assert_is_userdata' },
    -- type assertions: assertIsXXX -> assertXxx
    { 'assertIsNumber'          , 'assertNumber' },
    { 'assertIsString'          , 'assertString' },
    { 'assertIsTable'           , 'assertTable' },
    { 'assertIsBoolean'         , 'assertBoolean' },
    { 'assertIsNil'             , 'assertNil' },
    { 'assertIsFunction'        , 'assertFunction' },
    { 'assertIsThread'          , 'assertThread' },
    { 'assertIsUserdata'        , 'assertUserdata' },
    -- type assertions: assertIsXXX -> assert_xxx (luaunit v2 compat)
    { 'assertIsNumber'          , 'assert_number' },
    { 'assertIsString'          , 'assert_string' },
    { 'assertIsTable'           , 'assert_table' },
    { 'assertIsBoolean'         , 'assert_boolean' },
    { 'assertIsNil'             , 'assert_nil' },
    { 'assertIsFunction'        , 'assert_function' },
    { 'assertIsThread'          , 'assert_thread' },
    { 'assertIsUserdata'        , 'assert_userdata' },
    -- type assertions: assertNotIsXXX -> assert_not_is_xxx
    { 'assertNotIsNumber'       , 'assert_not_is_number' },
    { 'assertNotIsString'       , 'assert_not_is_string' },
    { 'assertNotIsTable'        , 'assert_not_is_table' },
    { 'assertNotIsBoolean'      , 'assert_not_is_boolean' },
    { 'assertNotIsNil'          , 'assert_not_is_nil' },
    { 'assertNotIsFunction'     , 'assert_not_is_function' },
    { 'assertNotIsThread'       , 'assert_not_is_thread' },
    { 'assertNotIsUserdata'     , 'assert_not_is_userdata' },
    -- type assertions: assertNotIsXXX -> assertNotXxx (luaunit v2 compat)
    { 'assertNotIsNumber'       , 'assertNotNumber' },
    { 'assertNotIsString'       , 'assertNotString' },
    { 'assertNotIsTable'        , 'assertNotTable' },
    { 'assertNotIsBoolean'      , 'assertNotBoolean' },
    { 'assertNotIsNil'          , 'assertNotNil' },
    { 'assertNotIsFunction'     , 'assertNotFunction' },
    { 'assertNotIsThread'       , 'assertNotThread' },
    { 'assertNotIsUserdata'     , 'assertNotUserdata' },
    -- type assertions: assertNotIsXXX -> assert_not_xxx
    { 'assertNotIsNumber'       , 'assert_not_number' },
    { 'assertNotIsString'       , 'assert_not_string' },
    { 'assertNotIsTable'        , 'assert_not_table' },
    { 'assertNotIsBoolean'      , 'assert_not_boolean' },
    { 'assertNotIsNil'          , 'assert_not_nil' },
    { 'assertNotIsFunction'     , 'assert_not_function' },
    { 'assertNotIsThread'       , 'assert_not_thread' },
    { 'assertNotIsUserdata'     , 'assert_not_userdata' },
    -- all assertions with Coroutine duplicate Thread assertions
    { 'assertIsThread'          , 'assertIsCoroutine' },
    { 'assertIsThread'          , 'assertCoroutine' },
    { 'assertIsThread'          , 'assert_is_coroutine' },
    { 'assertIsThread'          , 'assert_coroutine' },
    { 'assertNotIsThread'       , 'assertNotIsCoroutine' },
    { 'assertNotIsThread'       , 'assertNotCoroutine' },
    { 'assertNotIsThread'       , 'assert_not_is_coroutine' },
    { 'assertNotIsThread'       , 'assert_not_coroutine' },
}
-- Create all aliases in M
for _,v in ipairs( list_of_funcs ) do
    funcname, alias = v[1], v[2]
    M[alias] = M[funcname]
    if EXPORT_ASSERT_TO_GLOBALS then
        _G[funcname] = M[funcname]
        _G[alias] = M[funcname]
    end
end
----------------------------------------------------------------
--
--                     Outputters
--
----------------------------------------------------------------
----------------------------------------------------------------
--                     class TapOutput
----------------------------------------------------------------
local TapOutput = { __class__ = 'TapOutput' } -- class
local TapOutput_MT = { __index = TapOutput } -- metatable
    -- For a good reference for TAP format, check: http://testanything.org/tap-specification.html
    function TapOutput:new()
        return setmetatable( { verbosity = M.VERBOSITY_LOW }, TapOutput_MT)
    end
    function TapOutput:startSuite()
        print("1.."..self.result.testCount)
        print('# Started on '..self.result.startDate)
    end
    function TapOutput:startClass(className)
        if className ~= '[TestFunctions]' then
            print('# Starting class: '..className)
        end
    end
    function TapOutput:startTest(testName) end
    function TapOutput:addFailure( node )
        io.stdout:write("not ok ", self.result.currentTestNumber, "\t", node.testName, "\n")
        if self.verbosity > M.VERBOSITY_LOW then
           print( prefixString( '    ', node.msg ) )
        end
        if self.verbosity > M.VERBOSITY_DEFAULT then
           print( prefixString( '    ', node.stackTrace ) )
        end
    end
    TapOutput.addError = TapOutput.addFailure
    function TapOutput:endTest( node )
        if node:isPassed() then
            io.stdout:write("ok     ", self.result.currentTestNumber, "\t", node.testName, "\n")
        end
    end
    function TapOutput:endClass() end
    function TapOutput:endSuite()
        print( '# '..M.LuaUnit.statusLine( self.result ) )
        return self.result.notPassedCount
    end
-- class TapOutput end
----------------------------------------------------------------
--                     class JUnitOutput
----------------------------------------------------------------
-- See directory junitxml for more information about the junit format
local JUnitOutput = { __class__ = 'JUnitOutput' } -- class
local JUnitOutput_MT = { __index = JUnitOutput } -- metatable
    function JUnitOutput:new()
        return setmetatable(
            { testList = {}, verbosity = M.VERBOSITY_LOW }, JUnitOutput_MT)
    end
    function JUnitOutput:startSuite()
        -- open xml file early to deal with errors
        if self.fname == nil then
            error('With Junit, an output filename must be supplied with --name!')
        end
        if string.sub(self.fname,-4) ~= '.xml' then
            self.fname = self.fname..'.xml'
        end
        self.fd = io.open(self.fname, "w")
        if self.fd == nil then
            error("Could not open file for writing: "..self.fname)
        end
        print('# XML output to '..self.fname)
        print('# Started on '..self.result.startDate)
    end
    function JUnitOutput:startClass(className)
        if className ~= '[TestFunctions]' then
            print('# Starting class: '..className)
        end
    end
    function JUnitOutput:startTest(testName)
        print('# Starting test: '..testName)
    end
    function JUnitOutput:addFailure( node )
        print('# Failure: ' .. node.msg)
        -- print('# ' .. node.stackTrace)
    end
    function JUnitOutput:addError( node )
        print('# Error: ' .. node.msg)
        -- print('# ' .. node.stackTrace)
    end
    function JUnitOutput:endTest( node )
    end
    function JUnitOutput:endClass()
    end
    function JUnitOutput:endSuite()
        print( '# '..M.LuaUnit.statusLine(self.result))
        -- XML file writing
        self.fd:write('<?xml version="1.0" encoding="UTF-8" ?>\n')
        self.fd:write('<testsuites>\n')
        self.fd:write(string.format(
            '    <testsuite name="LuaUnit" id="00001" package="" hostname="localhost" tests="%d" timestamp="%s" time="%0.3f" errors="%d" failures="%d">\n',
            self.result.runCount, self.result.startIsodate, self.result.duration, self.result.errorCount, self.result.failureCount ))
        self.fd:write("        <properties>\n")
        self.fd:write(string.format('            <property name="Lua Version" value="%s"/>\n', _VERSION ) )
        self.fd:write(string.format('            <property name="LuaUnit Version" value="%s"/>\n', M.VERSION) )
        -- XXX please include system name and version if possible
        self.fd:write("        </properties>\n")
        for i,node in ipairs(self.result.tests) do
            self.fd:write(string.format('        <testcase classname="%s" name="%s" time="%0.3f">\n',
                node.className, node.testName, node.duration ) )
            if node:isNotPassed() then
                self.fd:write(node:statusXML())
            end
            self.fd:write('        </testcase>\n')
        end
        -- Next two lines are needed to validate junit ANT xsd, but really not useful in general:
        self.fd:write('    <system-out/>\n')
        self.fd:write('    <system-err/>\n')
        self.fd:write('    </testsuite>\n')
        self.fd:write('</testsuites>\n')
        self.fd:close()
        return self.result.notPassedCount
    end
-- class TapOutput end
----------------------------------------------------------------
--                     class TextOutput
----------------------------------------------------------------
--[[
-- Python Non verbose:
For each test: . or F or E
If some failed tests:
    ==============
    ERROR / FAILURE: TestName (testfile.testclass)
    ---------
    Stack trace
then --------------
then "Ran x tests in 0.000s"
then OK or FAILED (failures=1, error=1)
-- Python Verbose:
testname (filename.classname) ... ok
testname (filename.classname) ... FAIL
testname (filename.classname) ... ERROR
then --------------
then "Ran x tests in 0.000s"
then OK or FAILED (failures=1, error=1)
-- Ruby:
Started
 .
 Finished in 0.002695 seconds.
 1 tests, 2 assertions, 0 failures, 0 errors
-- Ruby:
>> ruby tc_simple_number2.rb
Loaded suite tc_simple_number2
Started
F..
Finished in 0.038617 seconds.
  1) Failure:
test_failure(TestSimpleNumber) [tc_simple_number2.rb:16]:
Adding doesn't work.
<3> expected but was
<4>.
3 tests, 4 assertions, 1 failures, 0 errors
-- Java Junit
.......F.
Time: 0,003
There was 1 failure:
1) testCapacity(junit.samples.VectorTest)junit.framework.AssertionFailedError
    at junit.samples.VectorTest.testCapacity(VectorTest.java:87)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
FAILURES!!!
Tests run: 8,  Failures: 1,  Errors: 0
-- Maven
# mvn test
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running math.AdditionTest
Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed:
0.03 sec <<< FAILURE!
Results :
Failed tests:
  testLireSymbole(math.AdditionTest)
Tests run: 2, Failures: 1, Errors: 0, Skipped: 0
-- LuaUnit
---- non verbose
* display . or F or E when running tests
---- verbose
* display test name + ok/fail
----
* blank line
* number) ERROR or FAILURE: TestName
   Stack trace
* blank line
* number) ERROR or FAILURE: TestName
   Stack trace
then --------------
then "Ran x tests in 0.000s (%d not selected, %d skipped)"
then OK or FAILED (failures=1, error=1)
]]
local TextOutput = { __class__ = 'TextOutput' } -- class
local TextOutput_MT = { __index = TextOutput } -- metatable
    function TextOutput:new()
        return setmetatable(
            { errorList = {}, verbosity = M.VERBOSITY_DEFAULT }, TextOutput_MT )
    end
    function TextOutput:startSuite()
        if self.verbosity > M.VERBOSITY_DEFAULT then
            print( 'Started on '.. self.result.startDate )
        end
    end
    function TextOutput:startClass(className)
        -- display nothing when starting a new class
    end
    function TextOutput:startTest(testName)
        if self.verbosity > M.VERBOSITY_DEFAULT then
            io.stdout:write( "    ", self.result.currentNode.testName, " ... " )
        end
    end
    function TextOutput:addFailure( node )
        -- nothing
    end
    function TextOutput:addError( node )
        -- nothing
    end
    function TextOutput:endTest( node )
        if node:isPassed() then
            if self.verbosity > M.VERBOSITY_DEFAULT then
                io.stdout:write("Ok\n")
            else
                io.stdout:write(".")
            end
        else
            if self.verbosity > M.VERBOSITY_DEFAULT then
                print( node.status )
                print( node.msg )
                --[[
                -- find out when to do this:
                if self.verbosity > M.VERBOSITY_DEFAULT then
                    print( node.stackTrace )
                end
                ]]
            else
                -- write only the first character of status
                io.stdout:write(string.sub(node.status, 1, 1))
            end
        end
    end
    function TextOutput:endClass()
        -- nothing
    end
    function TextOutput:displayOneFailedTest( index, failure )
        print(index..") "..failure.testName )
        print( failure.msg )
        print( failure.stackTrace )
        print()
    end
    function TextOutput:displayFailedTests()
        if self.result.notPassedCount == 0 then return end
        print("Failed tests:")
        print("-------------")
        for i,v in ipairs(self.result.notPassed) do
            self:displayOneFailedTest( i, v )
        end
    end
    function TextOutput:endSuite()
        if self.verbosity > M.VERBOSITY_DEFAULT then
            print("=========================================================")
        else
            print()
        end
        self:displayFailedTests()
        print( M.LuaUnit.statusLine( self.result ) )
        local ignoredString = ""
        if self.result.notPassedCount == 0 then
            print('OK')
        end
    end
-- class TextOutput end
----------------------------------------------------------------
--                     class NilOutput
----------------------------------------------------------------
local function nopCallable()
    --print(42)
    return nopCallable
end
local NilOutput = { __class__ = 'NilOuptut' } -- class
local NilOutput_MT = { __index = nopCallable } -- metatable
function NilOutput:new()
    return setmetatable( { __class__ = 'NilOutput' }, NilOutput_MT )
end
----------------------------------------------------------------
--
--                     class LuaUnit
--
----------------------------------------------------------------
M.LuaUnit = {
    outputType = TextOutput,
    verbosity = M.VERBOSITY_DEFAULT,
    __class__ = 'LuaUnit'
}
local LuaUnit_MT = { __index = M.LuaUnit }
if EXPORT_ASSERT_TO_GLOBALS then
    LuaUnit = M.LuaUnit
end
    function M.LuaUnit:new()
        return setmetatable( {}, LuaUnit_MT )
    end
    -----------------[[ Utility methods ]]---------------------
    function M.LuaUnit.asFunction(aObject)
        -- return "aObject" if it is a function, and nil otherwise
        if 'function' == type(aObject) then return aObject end
    end
    function M.LuaUnit.isClassMethod(aName)
        -- return true if aName contains a class + a method name in the form class:method
        return string.find(aName, '.', nil, true) ~= nil
    end
    function M.LuaUnit.splitClassMethod(someName)
        -- return a pair className, methodName for a name in the form class:method
        -- return nil if not a class + method name
        -- name is class + method
        local hasMethod, methodName, className
        hasMethod = string.find(someName, '.', nil, true )
        if not hasMethod then return nil end
        methodName = string.sub(someName, hasMethod+1)
        className = string.sub(someName,1,hasMethod-1)
        return className, methodName
    end
    function M.LuaUnit.isMethodTestName( s )
        -- return true is the name matches the name of a test method
        -- default rule is that is starts with 'Test' or with 'test'
        return string.sub(s, 1, 4):lower() == 'test'
    end
    function M.LuaUnit.isTestName( s )
        -- return true is the name matches the name of a test
        -- default rule is that is starts with 'Test' or with 'test'
        return string.sub(s, 1, 4):lower() == 'test'
    end
    function M.LuaUnit.collectTests()
        -- return a list of all test names in the global namespace
        -- that match LuaUnit.isTestName
        local testNames = {}
        for k, v in pairs(_G) do
            if M.LuaUnit.isTestName( k ) then
                table.insert( testNames , k )
            end
        end
        table.sort( testNames )
        return testNames
    end
    function M.LuaUnit.parseCmdLine( cmdLine )
        -- parse the command line
        -- Supported command line parameters:
        -- --verbose, -v: increase verbosity
        -- --quiet, -q: silence output
        -- --error, -e: treat errors as fatal (quit program)
        -- --output, -o, + name: select output type
        -- --pattern, -p, + pattern: run test matching pattern, may be repeated
        -- --name, -n, + fname: name of output file for junit, default to stdout
        -- [testnames, ...]: run selected test names
        --
        -- Returns a table with the following fields:
        -- verbosity: nil, M.VERBOSITY_DEFAULT, M.VERBOSITY_QUIET, M.VERBOSITY_VERBOSE
        -- output: nil, 'tap', 'junit', 'text', 'nil'
        -- testNames: nil or a list of test names to run
        -- pattern: nil or a list of patterns
        local result = {}
        local state = nil
        local SET_OUTPUT = 1
        local SET_PATTERN = 2
        local SET_FNAME = 3
        if cmdLine == nil then
            return result
        end
        local function parseOption( option )
            if option == '--help' or option == '-h' then
                result['help'] = true
                return
            elseif option == '--version' then
                result['version'] = true
                return
            elseif option == '--verbose' or option == '-v' then
                result['verbosity'] = M.VERBOSITY_VERBOSE
                return
            elseif option == '--quiet' or option == '-q' then
                result['verbosity'] = M.VERBOSITY_QUIET
                return
            elseif option == '--error' or option == '-e' then
                result['quitOnError'] = true
                return
            elseif option == '--failure' or option == '-f' then
                result['quitOnFailure'] = true
                return
            elseif option == '--output' or option == '-o' then
                state = SET_OUTPUT
                return state
            elseif option == '--name' or option == '-n' then
                state = SET_FNAME
                return state
            elseif option == '--pattern' or option == '-p' then
                state = SET_PATTERN
                return state
            end
            error('Unknown option: '..option,3)
        end
        local function setArg( cmdArg, state )
            if state == SET_OUTPUT then
                result['output'] = cmdArg
                return
            elseif state == SET_FNAME then
                result['fname'] = cmdArg
                return
            elseif state == SET_PATTERN then
                if result['pattern'] then
                    table.insert( result['pattern'], cmdArg )
                else
                    result['pattern'] = { cmdArg }
                end
                return
            end
            error('Unknown parse state: '.. state)
        end
        for i, cmdArg in ipairs(cmdLine) do
            if state ~= nil then
                setArg( cmdArg, state, result )
                state = nil
            else
                if cmdArg:sub(1,1) == '-' then
                    state = parseOption( cmdArg )
                else
                    if result['testNames'] then
                        table.insert( result['testNames'], cmdArg )
                    else
                        result['testNames'] = { cmdArg }
                    end
                end
            end
        end
        if result['help'] then
            M.LuaUnit.help()
        end
        if result['version'] then
            M.LuaUnit.version()
        end
        if state ~= nil then
            error('Missing argument after '..cmdLine[ #cmdLine ],2 )
        end
        return result
    end
    function M.LuaUnit.help()
        print(M.USAGE)
        os.exit(0)
    end
    function M.LuaUnit.version()
        os.exit(0)
    end
    function M.LuaUnit.patternInclude( patternFilter, expr )
        -- check if any of patternFilter is contained in expr. If so, return true.
        -- return false if None of the patterns are contained in expr
        -- if patternFilter is nil, return true (no filtering)
        if patternFilter == nil then
            return true
        end
        for i,pattern in ipairs(patternFilter) do
            if string.find(expr, pattern) then
                return true
            end
        end
        return false
    end
----------------------------------------------------------------
--                     class NodeStatus
----------------------------------------------------------------
    local NodeStatus = { __class__ = 'NodeStatus' } -- class
    local NodeStatus_MT = { __index = NodeStatus } -- metatable
    M.NodeStatus = NodeStatus
    -- values of status
    NodeStatus.PASS  = 'PASS'
    NodeStatus.FAIL  = 'FAIL'
    NodeStatus.ERROR = 'ERROR'
    function NodeStatus:new( number, testName, className )
        local t = { number = number, testName = testName, className = className }
        setmetatable( t, NodeStatus_MT )
        t:pass()
        return t
    end
    function NodeStatus:pass()
        self.status = self.PASS
        -- useless but we know it's the field we want to use
        self.msg = nil
        self.stackTrace = nil
    end
    function NodeStatus:fail(msg, stackTrace)
        self.status = self.FAIL
        self.msg = msg
        self.stackTrace = stackTrace
    end
    function NodeStatus:error(msg, stackTrace)
        self.status = self.ERROR
        self.msg = msg
        self.stackTrace = stackTrace
    end
    function NodeStatus:isPassed()
        return self.status == NodeStatus.PASS
    end
    function NodeStatus:isNotPassed()
        -- print('hasFailure: '..prettystr(self))
        return self.status ~= NodeStatus.PASS
    end
    function NodeStatus:isFailure()
        return self.status == NodeStatus.FAIL
    end
    function NodeStatus:isError()
        return self.status == NodeStatus.ERROR
    end
    function NodeStatus:statusXML()
        if self:isError() then
            return table.concat(
                {'            <error type="', xmlEscape(self.msg), '">\n',
                 '                <![CDATA[', xmlCDataEscape(self.stackTrace),
                 ']]></error>\n'})
        elseif self:isFailure() then
            return table.concat(
                {'            <failure type="', xmlEscape(self.msg), '">\n',
                 '                <![CDATA[', xmlCDataEscape(self.stackTrace),
                 ']]></failure>\n'})
        end
        return '            <passed/>\n' -- (not XSD-compliant! normally shouldn't get here)
    end
    --------------[[ Output methods ]]-------------------------
    function M.LuaUnit.statusLine(result)
        -- return status line string according to results
        local s = string.format('Ran %d tests in %0.3f seconds, %d successes',
            result.runCount, result.duration, result.passedCount )
        if result.notPassedCount > 0 then
            if result.failureCount > 0 then
                s = s..string.format(', %d failures', result.failureCount )
            end
            if result.errorCount > 0 then
                s = s..string.format(', %d errors', result.errorCount )
            end
        else
            s = s..', 0 failures'
        end
        if result.nonSelectedCount > 0 then
            s = s..string.format(", %d non-selected", result.nonSelectedCount )
        end
        return s
    end
    function M.LuaUnit:startSuite(testCount, nonSelectedCount)
        self.result = {}
        self.result.testCount = testCount
        self.result.nonSelectedCount = nonSelectedCount
        self.result.passedCount = 0
        self.result.runCount = 0
        self.result.currentTestNumber = 0
        self.result.currentClassName = ""
        self.result.currentNode = nil
        self.result.suiteStarted = true
        self.result.startTime = os.clock()
        self.result.startDate = os.date(os.getenv('LUAUNIT_DATEFMT'))
        self.result.startIsodate = os.date('%Y-%m-%dT%H:%M:%S')
        self.result.patternFilter = self.patternFilter
        self.result.tests = {}
        self.result.failures = {}
        self.result.errors = {}
        self.result.notPassed = {}
        self.outputType = self.outputType or TextOutput
        self.output = self.outputType:new()
        self.output.runner = self
        self.output.result = self.result
        self.output.verbosity = self.verbosity
        self.output.fname = self.fname
        self.output:startSuite()
    end
    function M.LuaUnit:startClass( className )
        self.result.currentClassName = className
        self.output:startClass( className )
    end
    function M.LuaUnit:startTest( testName  )
        self.result.currentTestNumber = self.result.currentTestNumber + 1
        self.result.runCount = self.result.runCount + 1
        self.result.currentNode = NodeStatus:new(
            self.result.currentTestNumber,
            testName,
            self.result.currentClassName
        )
        self.result.currentNode.startTime = os.clock()
        table.insert( self.result.tests, self.result.currentNode )
        self.output:startTest( testName )
    end
    function M.LuaUnit:addStatus( err )
        -- "err" is expected to be a table / result from protectedCall()
        if err.status == NodeStatus.PASS then return end
        local node = self.result.currentNode
        --[[ As a first approach, we will report only one error or one failure for one test.
        However, we can have the case where the test is in failure, and the teardown is in error.
        In such case, it's a good idea to report both a failure and an error in the test suite. This is
        what Python unittest does for example. However, it mixes up counts so need to be handled carefully: for
        example, there could be more (failures + errors) count that tests. What happens to the current node ?
        We will do this more intelligent version later.
        ]]
        -- if the node is already in failure/error, just don't report the new error (see above)
        if node.status ~= NodeStatus.PASS then return end
        table.insert( self.result.notPassed, node )
        if err.status == NodeStatus.FAIL then
            node:fail( err.msg, err.trace )
            table.insert( self.result.failures, node )
            self.output:addFailure( node )
        elseif err.status == NodeStatus.ERROR then
            node:error( err.msg, err.trace )
            table.insert( self.result.errors, node )
            self.output:addError( node )
        end
    end
    function M.LuaUnit:endTest()
        local node = self.result.currentNode
        -- print( 'endTest() '..prettystr(node))
        -- print( 'endTest() '..prettystr(node:isNotPassed()))
        node.duration = os.clock() - node.startTime
        node.startTime = nil
        self.output:endTest( node )
        if node:isPassed() then
            self.result.passedCount = self.result.passedCount + 1
        elseif node:isError() then
            if self.quitOnError or self.quitOnFailure then
                -- Runtime error - abort test execution as requested by
                -- "--error" option. This is done by setting a special
                -- flag that gets handled in runSuiteByInstances().
                print("\nERROR during LuaUnit test execution:\n" .. node.msg)
                self.result.aborted = true
            end
        elseif node:isFailure() then
            if self.quitOnFailure then
                -- Failure - abort test execution as requested by
                -- "--failure" option. This is done by setting a special
                -- flag that gets handled in runSuiteByInstances().
                print("\nFailure during LuaUnit test execution:\n" .. node.msg)
                self.result.aborted = true
            end
        end
        self.result.currentNode = nil
    end
    function M.LuaUnit:endClass()
        self.output:endClass()
    end
    function M.LuaUnit:endSuite()
        if self.result.suiteStarted == false then
            error('LuaUnit:endSuite() -- suite was already ended' )
        end
        self.result.duration = os.clock()-self.result.startTime
        self.result.suiteStarted = false
        -- Expose test counts for outputter's endSuite(). This could be managed
        -- internally instead, but unit tests (and existing use cases) might
        -- rely on these fields being present.
        self.result.notPassedCount = #self.result.notPassed
        self.result.failureCount = #self.result.failures
        self.result.errorCount = #self.result.errors
        self.output:endSuite()
    end
    function M.LuaUnit:setOutputType(outputType)
        -- default to text
        -- tap produces results according to TAP format
        if outputType:upper() == "NIL" then
            self.outputType = NilOutput
            return
        end
        if outputType:upper() == "TAP" then
            self.outputType = TapOutput
            return
        end
        if outputType:upper() == "JUNIT" then
            self.outputType = JUnitOutput
            return
        end
        if outputType:upper() == "TEXT" then
            self.outputType = TextOutput
            return
        end
        error( 'No such format: '..outputType,2)
    end
    --------------[[ Runner ]]-----------------
    function M.LuaUnit:protectedCall(classInstance, methodInstance, prettyFuncName)
        -- if classInstance is nil, this is just a function call
        -- else, it's method of a class being called.
        local function err_handler(e)
            -- transform error into a table, adding the traceback information
            return {
                status = NodeStatus.ERROR,
                msg = e,
                trace = string.sub(debug.traceback("", 3), 2)
            }
        end
        local ok, err
        if classInstance then
            -- stupid Lua < 5.2 does not allow xpcall with arguments so let's use a workaround
            ok, err = xpcall( function () methodInstance(classInstance) end, err_handler )
        else
            ok, err = xpcall( function () methodInstance() end, err_handler )
        end
        if ok then
            return {status = NodeStatus.PASS}
        end
        -- determine if the error was a failed test:
        -- We do this by stripping the failure prefix from the error message,
        -- while keeping track of the gsub() count. A non-zero value -> failure
        local failed
        err.msg, failed = err.msg:gsub(M.FAILURE_PREFIX, "", 1)
        if failed > 0 then
            err.status = NodeStatus.FAIL
        end
        -- reformat / improve the stack trace
        if prettyFuncName then -- we do have the real method name
            err.trace = err.trace:gsub("in (%a+) 'methodInstance'", "in %1 '"..prettyFuncName.."'")
        end
        if STRIP_LUAUNIT_FROM_STACKTRACE then
            err.trace = stripLuaunitTrace(err.trace)
        end
        return err -- return the error "object" (table)
    end
    function M.LuaUnit:execOneFunction(className, methodName, classInstance, methodInstance)
        -- When executing a test function, className and classInstance must be nil
        -- When executing a class method, all parameters must be set
        if type(methodInstance) ~= 'function' then
            error( tostring(methodName)..' must be a function, not '..type(methodInstance))
        end
        local prettyFuncName
        if className == nil then
            className = '[TestFunctions]'
            prettyFuncName = methodName
        else
            prettyFuncName = className..'.'..methodName
        end
        if self.lastClassName ~= className then
            if self.lastClassName ~= nil then
                self:endClass()
            end
            self:startClass( className )
            self.lastClassName = className
        end
        self:startTest(prettyFuncName)
        -- run setUp first (if any)
        if classInstance then
            local func = self.asFunction( classInstance.setUp )
                         or self.asFunction( classInstance.Setup )
                         or self.asFunction( classInstance.setup )
                         or self.asFunction( classInstance.SetUp )
            if func then
                self:addStatus(self:protectedCall(classInstance, func, className..'.setUp'))
            end
        end
        -- run testMethod()
        if self.result.currentNode:isPassed() then
            self:addStatus(self:protectedCall(classInstance, methodInstance, prettyFuncName))
        end
        -- lastly, run tearDown (if any)
        if classInstance then
            local func = self.asFunction( classInstance.tearDown )
                         or self.asFunction( classInstance.TearDown )
                         or self.asFunction( classInstance.teardown )
                         or self.asFunction( classInstance.Teardown )
            if func then
                self:addStatus(self:protectedCall(classInstance, func, className..'.tearDown'))
            end
        end
        self:endTest()
    end
    function M.LuaUnit.expandOneClass( result, className, classInstance )
        -- add all test methods of classInstance to result
        for methodName, methodInstance in sortedPairs(classInstance) do
            if M.LuaUnit.asFunction(methodInstance) and M.LuaUnit.isMethodTestName( methodName ) then
                table.insert( result, { className..'.'..methodName, classInstance } )
            end
        end
    end
    function M.LuaUnit.expandClasses( listOfNameAndInst )
        -- expand all classes (provided as {className, classInstance}) to a list of {className.methodName, classInstance}
        -- functions and methods remain untouched
        local result = {}
        for i,v in ipairs( listOfNameAndInst ) do
            local name, instance = v[1], v[2]
            if M.LuaUnit.asFunction(instance) then
                table.insert( result, { name, instance } )
            else
                if type(instance) ~= 'table' then
                    error( 'Instance must be a table or a function, not a '..type(instance)..', value '..prettystr(instance))
                end
                if M.LuaUnit.isClassMethod( name ) then
                    local className, methodName = M.LuaUnit.splitClassMethod( name )
                    local methodInstance = instance[methodName]
                    if methodInstance == nil then
                        error( "Could not find method in class "..tostring(className).." for method "..tostring(methodName) )
                    end
                    table.insert( result, { name, instance } )
                else
                    M.LuaUnit.expandOneClass( result, name, instance )
                end
            end
        end
        return result
    end
    function M.LuaUnit.applyPatternFilter( patternFilter, listOfNameAndInst )
        local included, excluded = {}, {}
        for i, v in ipairs( listOfNameAndInst ) do
            -- local name, instance = v[1], v[2]
            if M.LuaUnit.patternInclude( patternFilter, 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. All test instances and names must be supplied.
        -- each test must be one of:
        --   * { function name, function instance }
        --   * { class name, class instance }
        --   * { class.method name, class instance }
        local expandedList, filteredList, filteredOutList, className, methodName, methodInstance
        expandedList = self.expandClasses( listOfNameAndInst )
        filteredList, filteredOutList = self.applyPatternFilter( self.patternFilter, 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
                if type(instance) ~= 'table' then
                    error( 'Instance must be a table or a function, not a '..type(instance)..', value '..prettystr(instance))
                else
                    assert( M.LuaUnit.isClassMethod( name ) )
                    className, methodName = M.LuaUnit.splitClassMethod( name )
                    methodInstance = instance[methodName]
                    if methodInstance == nil then
                        error( "Could not find method in class "..tostring(className).." for method "..tostring(methodName) )
                    end
                    self:execOneFunction( className, methodName, instance, methodInstance )
                end
            end
            if self.result.aborted then break end -- "--error" or "--failure" option triggered
        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 an explicit list of test names
        local  className, methodName, instanceName, instance, methodInstance
        local listOfNameAndInst = {}
        for i,name in ipairs( listOfName ) do
            if M.LuaUnit.isClassMethod( name ) then
                className, methodName = M.LuaUnit.splitClassMethod( name )
                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
                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 no_error, val = pcall( M.LuaUnit.parseCmdLine, args )
        if not no_error then
            print(val) -- error message
            print()
            print(M.USAGE)
            os.exit(-1)
        end
        local options = val
        -- 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.patternFilter = options.pattern
        if options.output and 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
        if options.output then
            no_error, val = pcall(self.setOutputType, self, options.output)
            if not no_error then
                print(val) -- error message
                print()
                print(M.USAGE)
                os.exit(-1)
            end
        end
        self:runSuiteByNames( options.testNames or M.LuaUnit.collectTests() )
        return self.result.notPassedCount
    end
-- class LuaUnit
-- For compatibility 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


local suite = require("test_helper")
local ffi = require("ffi")
local TestUprobes = {}

ffi.cdef[[
  int getpid(void);
  void malloc_stats(void);
]]

function TestUprobes:test_simple_library()
  local text = [[
#include <uapi/linux/ptrace.h>
BPF_ARRAY(stats, u64, 1);
static void incr(int idx) {
    u64 *ptr = stats.lookup(&idx);
    if (ptr)
        ++(*ptr);
}
int count(struct pt_regs *ctx) {
    u32 pid = bpf_get_current_pid_tgid();
    if (pid == PID)
        incr(0);
    return 0;
}]]

  local pid = tonumber(ffi.C.getpid())
  local text = text:gsub("PID", tostring(pid))

  local b = BPF:new{text=text}
  b:attach_uprobe{name="c", sym="malloc_stats", fn_name="count", pid=pid}
  b:attach_uprobe{name="c", sym="malloc_stats", fn_name="count", pid=pid, retprobe=true}

  assert_equals(BPF.num_open_uprobes(), 2)

  ffi.C.malloc_stats()

  local stats = b:get_table("stats")
  assert_equals(tonumber(stats:get(0)), 2)
end

function TestUprobes:test_simple_binary()
  local text = [[
#include <uapi/linux/ptrace.h>
BPF_ARRAY(stats, u64, 1);
static void incr(int idx) {
    u64 *ptr = stats.lookup(&idx);
    if (ptr)
        ++(*ptr);
}
int count(struct pt_regs *ctx) {
    u32 pid = bpf_get_current_pid_tgid();
    incr(0);
    return 0;
}]]

  local b = BPF:new{text=text}
  local pythonpath = "/usr/bin/python3"
  local symname = "_start"
  b:attach_uprobe{name=pythonpath, sym=symname, fn_name="count"}
  b:attach_uprobe{name=pythonpath, sym=symname, fn_name="count", retprobe=true}

  os.spawn(pythonpath .. " -V")

  local stats = b:get_table("stats")
  assert_true(tonumber(stats:get(0)) >= 2)
end

function TestUprobes:teardown()
  BPF.cleanup()
end

suite("TestUprobes", TestUprobes)


local suite = require("test_helper")
local TestDump = {}

function TestDump:test_dump_func()
  local raw = "\xb7\x00\x00\x00\x01\x00\x00\x00\x95\x00\x00\x00\x00\x00\x00\x00"
  local b = BPF:new{text=[[int entry(void) { return 1; }]]}
  assert_equals(b:dump_func("entry"), raw)
end

suite("TestDump", TestDump)


local suite = require("test_helper")
local TestClang = {}

function TestClang:test_probe_read1()
  local text = [[
#include <linux/sched.h>
#include <uapi/linux/ptrace.h>
int count_sched(struct pt_regs *ctx, struct task_struct *prev) {
    pid_t p = prev->pid;
    return (p != -1);
}
]]
  local b = BPF:new{text=text, debug=0}
  local fn = b:load_func("count_sched", 'BPF_PROG_TYPE_KPROBE')
end

function TestClang:test_probe_read2()
  local text = [[
#include <linux/sched.h>
#include <uapi/linux/ptrace.h>
int count_foo(struct pt_regs *ctx, unsigned long a, unsigned long b) {
    return (a != b);
}
]]
  local b = BPF:new{text=text, debug=0}
  local fn = b:load_func("count_foo", 'BPF_PROG_TYPE_KPROBE')
end

function TestClang:test_probe_read_keys()
  local text = [[
#include <uapi/linux/ptrace.h>
#include <linux/blkdev.h>
BPF_HASH(start, struct request *);
int do_request(struct pt_regs *ctx, struct request *req) {
  u64 ts = bpf_ktime_get_ns();
  start.update(&req, &ts);
  return 0;
}

int do_completion(struct pt_regs *ctx, struct request *req) {
  u64 *tsp = start.lookup(&req);
  if (tsp != 0) {
    start.delete(&req);
  }
  return 0;
}
  ]]
  local b = BPF:new{text=text, debug=0}
  local fns = b:load_funcs('BPF_PROG_TYPE_KPROBE')
end

function TestClang:test_sscanf()
  local text = [[
BPF_HASH(stats, int, struct { u64 a; u64 b; u32 c:18; u32 d:14; struct { u32 a; u32 b; } s; }, 10);

int foo(void *ctx) {
    return 0;
}
]]
  local b = BPF:new{text=text, debug=0}
  local fn = b:load_func("foo", 'BPF_PROG_TYPE_KPROBE')
  local t = b:get_table("stats")
  local s1 = t:key_sprintf(2)

  assert_equals(s1, "0x2")

  local s2 = t:leaf_sprintf({{2, 3, 4, 1, {5, 6}}})
  local l = t:leaf_scanf(s2)

  assert_equals(tonumber(l.a), 2)
  assert_equals(tonumber(l.b), 3)
  assert_equals(tonumber(l.c), 4)
  assert_equals(tonumber(l.d), 1)
  assert_equals(tonumber(l.s.a), 5)
  assert_equals(tonumber(l.s.b), 6)
end

function TestClang:test_sscanf_array()
  local text = [[ BPF_HASH(stats, int, struct { u32 a[3]; u32 b; }, 10); ]]

  local b = BPF:new{text=text, debug=0}
  local t = b:get_table("stats")

  local s1 = t:key_sprintf(2)
  assert_equals(s1, "0x2")

  local s2 = t:leaf_sprintf({{{1, 2, 3}, 4}})
  assert_equals(s2, "{ [ 0x1 0x2 0x3 ] 0x4 }")

  local l = t:leaf_scanf(s2)
  assert_equals(l.a[0], 1)
  assert_equals(l.a[1], 2)
  assert_equals(l.a[2], 3)
  assert_equals(l.b, 4)
end

function TestClang:test_iosnoop()
  local text = [[
#include <linux/blkdev.h>
#include <uapi/linux/ptrace.h>

struct key_t {
    struct request *req;
};

BPF_HASH(start, struct key_t, u64, 1024);
int do_request(struct pt_regs *ctx, struct request *req) {
    struct key_t key = {};

    bpf_trace_printk("traced start %d\\n", req->__data_len);

    return 0;
}
]]

  local b = BPF:new{text=text, debug=0}
  local fn = b:load_func("do_request", 'BPF_PROG_TYPE_KPROBE')
end

function TestClang:test_blk_start_request()
  local text = [[
#include <linux/blkdev.h>
#include <uapi/linux/ptrace.h>
int do_request(struct pt_regs *ctx, int req) {
    bpf_trace_printk("req ptr: 0x%x\n", req);
    return 0;
}
]]
  local b = BPF:new{text=text, debug=0}
  local fn = b:load_func("do_request", 'BPF_PROG_TYPE_KPROBE')
end

function TestClang:test_bpf_hash()
  local text = [[
BPF_HASH(table1);
BPF_HASH(table2, u32);
BPF_HASH(table3, u32, int);
]]
  local b = BPF:new{text=text, debug=0}
end

function TestClang:test_consecutive_probe_read()
  local text = [[
#include <linux/fs.h>
#include <linux/mount.h>
BPF_HASH(table1, struct super_block *);
int trace_entry(struct pt_regs *ctx, struct file *file) {
    if (!file) return 0;
    struct vfsmount *mnt = file->f_path.mnt;
    if (mnt) {
        struct super_block *k = mnt->mnt_sb;
        u64 zero = 0;
        table1.update(&k, &zero);
        k = mnt->mnt_sb;
        table1.update(&k, &zero);
    }

    return 0;
}
]]
  local b = BPF:new{text=text, debug=0}
  local fn = b:load_func("trace_entry", 'BPF_PROG_TYPE_KPROBE')
end

function TestClang:test_nested_probe_read()
  local text = [[
#include <linux/fs.h>
int trace_entry(struct pt_regs *ctx, struct file *file) {
    if (!file) return 0;
    const char *name = file->f_path.dentry->d_name.name;
    bpf_trace_printk("%s\\n", name);
    return 0;
}
]]
  local b = BPF:new{text=text, debug=0}
  local fn = b:load_func("trace_entry", 'BPF_PROG_TYPE_KPROBE')
end

function TestClang:test_char_array_probe()
  local b = BPF:new{text=[[#include <linux/blkdev.h>
int kprobe__blk_update_request(struct pt_regs *ctx, struct request *req) {
    bpf_trace_printk("%s\\n", req->rq_disk->disk_name);
    return 0;
}]]}
end

function TestClang:test_probe_read_helper()
  local b = BPF:new{text=[[
#include <linux/fs.h>
static void print_file_name(struct file *file) {
    if (!file) return;
    const char *name = file->f_path.dentry->d_name.name;
    bpf_trace_printk("%s\\n", name);
}
static void print_file_name2(int unused, struct file *file) {
    print_file_name(file);
}
int trace_entry1(struct pt_regs *ctx, struct file *file) {
    print_file_name(file);
    return 0;
}
int trace_entry2(struct pt_regs *ctx, int unused, struct file *file) {
    print_file_name2(unused, file);
    return 0;
}
]]}
  local fn1 = b:load_func("trace_entry1", 'BPF_PROG_TYPE_KPROBE')
  local fn2 = b:load_func("trace_entry2", 'BPF_PROG_TYPE_KPROBE')
end

function TestClang:test_probe_struct_assign()
  local b = BPF:new{text = [[
#include <uapi/linux/ptrace.h>
struct args_t {
    const char *filename;
    int flags;
    int mode;
};
int kprobe__sys_open(struct pt_regs *ctx, const char *filename,
        int flags, int mode) {
    struct args_t args = {};
    args.filename = filename;
    args.flags = flags;
    args.mode = mode;
    bpf_trace_printk("%s\\n", args.filename);
    return 0;
};
]]}
end

function TestClang:test_task_switch()
  local b = BPF:new{text=[[
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
struct key_t {
  u32 prev_pid;
  u32 curr_pid;
};
BPF_HASH(stats, struct key_t, u64, 1024);
int kprobe__finish_task_switch(struct pt_regs *ctx, struct task_struct *prev) {
  struct key_t key = {};
  u64 zero = 0, *val;
  key.curr_pid = bpf_get_current_pid_tgid();
  key.prev_pid = prev->pid;

  val = stats.lookup_or_try_init(&key, &zero);
  if (val) {
    (*val)++;
  }
  return 0;
}
]]}
end

function TestClang:test_probe_simple_assign()
  local b = BPF:new{text=[[
#include <uapi/linux/ptrace.h>
#include <linux/gfp.h>
struct leaf { size_t size; };
BPF_HASH(simple_map, u32, struct leaf);
int kprobe____kmalloc(struct pt_regs *ctx, size_t size) {
    u32 pid = bpf_get_current_pid_tgid();
    struct leaf* leaf = simple_map.lookup(&pid);
    if (leaf)
        leaf->size += size;
    return 0;
}]]}
end

function TestClang:test_unop_probe_read()
  local text = [[
#include <linux/blkdev.h>
int trace_entry(struct pt_regs *ctx, struct request *req) {
    if (!(req->bio->bi_flags & 1))
        return 1;
    if (((req->bio->bi_flags)))
        return 1;
    return 0;
}
]]
  local b = BPF:new{text=text}
  local fn = b:load_func("trace_entry", 'BPF_PROG_TYPE_KPROBE')
end

function TestClang:test_complex_leaf_types()
  local text = [[
struct list;
struct list {
  struct list *selfp;
  struct list *another_selfp;
  struct list *selfp_array[2];
};
struct empty {
};
union emptyu {
  struct empty *em1;
  struct empty em2;
  struct empty em3;
  struct empty em4;
};
BPF_ARRAY(t1, struct list, 1);
BPF_ARRAY(t2, struct list *, 1);
BPF_ARRAY(t3, union emptyu, 1);
]]
  local b = BPF:new{text=text}
  local ffi = require("ffi")

  -- TODO: ptrs?
  assert_equals(ffi.sizeof(b:get_table("t3").c_leaf), 8)
end

function TestClang:test_cflags()
  local text = [[
#ifndef MYFLAG
#error "MYFLAG not set as expected"
#endif
]]
  local b = BPF:new{text=text, cflags={"-DMYFLAG"}}
end

function TestClang:test_exported_maps()
  local b1 = BPF{text=[[BPF_TABLE_PUBLIC("hash", int, int, table1, 10);]]}
  local b2 = BPF{text=[[BPF_TABLE("extern", int, int, table1, 10);]]}
end

function TestClang:test_syntax_error()
  assert_error_msg_contains(
    "failed to compile BPF module",
    BPF.new,
    BPF, {text=[[int failure(void *ctx) { if (); return 0; }]]})
end

suite("TestClang", TestClang)


run

--[[
]]
return function()
  require("bcc.vendor.helpers")
  local standalone = rawget(_G, "BCC_STANDALONE")
  local progname = standalone or "bcc-probe"
  local function print_usage()
    io.stderr:write(string.format(
      "usage: %s [[--version|--verbose] --] path_to_script.lua [...]\n",
      progname))
    os.exit(1)
  end
  local function print_version()
    local jit = require("jit")
    print(string.format("%s %s -- Running on %s (%s/%s)",
      progname, rawget(_G, "BCC_VERSION") or "HEAD",
      jit.version, jit.os, jit.arch))
    os.exit(0)
  end
  while arg[1] and string.starts(arg[1], "-") do
    local k = table.remove(arg, 1)
    if k == "--" then
      break
    elseif standalone == nil and string.starts(k, "--so-path=") then
      rawset(_G, "LIBBCC_SO_PATH", string.lstrip(k, "--so-path="))
    elseif k == "--llvm-debug" then
      rawset(_G, "LIBBCC_LLVM_DEBUG", 1)
    elseif k == "-V" or k == "--verbose" then
      log.enabled = true
    elseif k == "-v" or k == "--version" then
      print_version()
    else
      print_usage()
    end
  end
  local tracefile = table.remove(arg, 1)
  if not tracefile then print_usage() end
  local BPF = require("bcc.bpf")
  BPF.script_root(tracefile)
  local USDT = require("bcc.usdt")
  local utils = {
    argparse = require("bcc.vendor.argparse"),
    posix = require("bcc.vendor.posix"),
    USDT = USDT,
  }
  local command = dofile(tracefile)
  local res, err = xpcall(command, debug.traceback, BPF, utils)
  if not res and err ~= "interrupted!" then
    io.stderr:write("[ERROR] "..err.."\n")
  end
  BPF.cleanup()
  USDT.cleanup()
  return res, err
end