z_mirror/flib

git clone https://git.raiguard.me/z_mirror/flib

Caleb HeuerApply stylua to all filesfe58c74

trunk
114.6 KiB3595 linesraw
1local ok, faketorio_path = pcall(require, "tests.faketorio_path")
2if ok then
3  dofile(faketorio_path)
4else
5  if not _ENV._FAKETORIO_globals then
6    _ENV._FAKETORIO_globals = true
7
8    _ENV.defines = {
9      direction = {
10        north = 0,
11        northeast = 1,
12        east = 2,
13        southeast = 3,
14        south = 4,
15        southwest = 5,
16        west = 6,
17        northwest = 7,
18      },
19    }
20    _ENV.settings = {
21      startup = {
22        __index = function()
23          return { value = true }
24        end,
25      },
26      global = {
27        __index = function()
28          return { value = true }
29        end,
30      },
31      player = {},
32    }
33    setmetatable(_ENV.settings.startup, _ENV.settings.startup)
34    setmetatable(_ENV.settings.global, _ENV.settings.global)
35
36    _ENV.log = function(...) end
37
38    _ENV.table_size = function(tbl)
39      local count = 0
40      for _ in pairs(tbl) do
41        count = count + 1
42      end
43      return count
44    end
45  end
46
47  if not _ENV._FAKETORIO_searcher then
48    _ENV._FAKETORIO_searcher = true
49
50    do
51      local paths = {}
52      for str in string.gmatch(package.path, "([^;]+)") do
53        table.insert(paths, str)
54      end
55      local path = debug.getinfo(1, "S").source:sub(2, -19)
56      paths[#paths + 1] = path .. "?.lua"
57      paths[#paths + 1] = path .. "?/init.lua"
58      paths[#paths + 1] = path .. "faketorio/lualib/?.lua"
59      paths[#paths + 1] = "./?/init.lua"
60      paths[#paths + 1] = "../?.lua"
61      paths[#paths + 1] = "../?/init.lua"
62
63      local patterns = { "^%./%?", "^%.%.", "^/h", "^/u", "." }
64      local function special_sort(a, b)
65        for _, pattern in ipairs(patterns) do
66          local A = string.find(a, pattern)
67          local B = string.find(b, pattern)
68          if A and not B then
69            return true
70          end
71          if B and not A then
72            return false
73          end
74          if A and B then
75            return a < b
76          end
77        end
78        return false
79      end
80      table.sort(paths, special_sort)
81      package.path = table.concat(paths, ";")
82    end
83
84    local function mod_searcher(original)
85      -- First search for `modname.some.path`
86      local modname = original:gsub("%_%_(%w+)%_%_", "%1")
87      local filepath = package.searchpath(modname, package.path, ".", "/")
88      if not filepath then
89        -- Then search for `some.path`
90        modname = original:gsub("%_%_(%w+)%_%_[%.%\\]", "")
91        filepath = package.searchpath(modname, package.path, ".", "/")
92        if not filepath then
93          return nil
94        end
95      end
96      local loader = loadfile(filepath)
97      return loader
98    end
99    table.insert(package.searchers, mod_searcher)
100  end
101end
102
103---@diagnostic disable
104--[[
105        luaunit.lua
106
107Description: A unit testing framework
108Homepage: https://github.com/bluebird75/luaunit
109Development by Philippe Fremy <phil@freehackers.org>
110Based on initial work of Ryu, Gwang (http://www.gpgstudy.com/gpgiki/LuaUnit)
111License: BSD License, see LICENSE.txt
112]]
113--
114
115local M = {}
116
117-- private exported functions (for testing)
118M.private = {}
119
120M.VERSION = "3.4"
121M._VERSION = M.VERSION -- For LuaUnit v2 compatibility
122
123-- a version which distinguish between regular Lua and LuaJit
124M._LUAVERSION = (jit and jit.version) or _VERSION
125
126--[[ Some people like assertEquals( actual, expected ) and some people prefer
127assertEquals( expected, actual ).
128]]
129--
130M.ORDER_ACTUAL_EXPECTED = true
131M.PRINT_TABLE_REF_IN_ERROR_MSG = false
132M.LINE_LENGTH = 80
133M.TABLE_DIFF_ANALYSIS_THRESHOLD = 10 -- display deep analysis for more than 10 items
134M.LIST_DIFF_ANALYSIS_THRESHOLD = 10 -- display deep analysis for more than 10 items
135
136-- this setting allow to remove entries from the stack-trace, for
137-- example to hide a call to a framework which would be calling luaunit
138M.STRIP_EXTRA_ENTRIES_IN_STACK_TRACE = 0
139
140--[[ EPS is meant to help with Lua's floating point math in simple corner
141cases like almostEquals(1.1-0.1, 1), which may not work as-is (e.g. on numbers
142with rational binary representation) if the user doesn't provide some explicit
143error margin.
144
145The default margin used by almostEquals() in such cases is EPS; and since
146Lua may be compiled with different numeric precisions (single vs. double), we
147try to select a useful default for it dynamically. Note: If the initial value
148is not acceptable, it can be changed by the user to better suit specific needs.
149
150See also: https://en.wikipedia.org/wiki/Machine_epsilon
151]]
152M.EPS = 2 ^ -52 -- = machine epsilon for "double", ~2.22E-16
153if math.abs(1.1 - 1 - 0.1) > M.EPS then
154  -- rounding error is above EPS, assume single precision
155  M.EPS = 2 ^ -23 -- = machine epsilon for "float", ~1.19E-07
156end
157
158-- set this to false to debug luaunit
159local STRIP_LUAUNIT_FROM_STACKTRACE = true
160
161M.VERBOSITY_DEFAULT = 10
162M.VERBOSITY_LOW = 1
163M.VERBOSITY_QUIET = 0
164M.VERBOSITY_VERBOSE = 20
165M.DEFAULT_DEEP_ANALYSIS = nil
166M.FORCE_DEEP_ANALYSIS = true
167M.DISABLE_DEEP_ANALYSIS = false
168
169-- set EXPORT_ASSERT_TO_GLOBALS to have all asserts visible as global values
170-- EXPORT_ASSERT_TO_GLOBALS = true
171
172-- we need to keep a copy of the script args before it is overriden
173local cmdline_argv = rawget(_G, "arg")
174
175M.FAILURE_PREFIX = "LuaUnit test FAILURE: " -- prefix string for failed tests
176M.SUCCESS_PREFIX = "LuaUnit test SUCCESS: " -- prefix string for successful tests finished early
177M.SKIP_PREFIX = "LuaUnit test SKIP:    " -- prefix string for skipped tests
178
179M.USAGE = [[Usage: lua <your_test_suite.lua> [options] [testname1 [testname2] ... ]
180Options:
181  -h, --help:             Print this help
182  --version:              Print version information
183  -v, --verbose:          Increase verbosity
184  -q, --quiet:            Set verbosity to minimum
185  -e, --error:            Stop on first error
186  -f, --failure:          Stop on first failure or error
187  -s, --shuffle:          Shuffle tests before running them
188  -o, --output OUTPUT:    Set output type to OUTPUT
189                          Possible values: text, tap, junit, nil
190  -n, --name NAME:        For junit only, mandatory name of xml file
191  -r, --repeat NUM:       Execute all tests NUM times, e.g. to trig the JIT
192  -p, --pattern PATTERN:  Execute all test names matching the Lua PATTERN
193                          May be repeated to include several patterns
194                          Make sure you escape magic chars like +? with %
195  -x, --exclude PATTERN:  Exclude all test names matching the Lua PATTERN
196                          May be repeated to exclude several patterns
197                          Make sure you escape magic chars like +? with %
198  testname1, testname2, ... : tests to run in the form of testFunction,
199                              TestClass or TestClass.testMethod
200
201You may also control LuaUnit options with the following environment variables:
202* LUAUNIT_OUTPUT: same as --output
203* LUAUNIT_JUNIT_FNAME: same as --name ]]
204
205----------------------------------------------------------------
206--
207--                 general utility functions
208--
209----------------------------------------------------------------
210
211--[[ Note on catching exit
212
213I have seen the case where running a big suite of test cases and one of them would
214perform a os.exit(0), making the outside world think that the full test suite was executed
215successfully.
216
217This is an attempt to mitigate this problem: we override os.exit() to now let a test
218exit the framework while we are running. When we are not running, it behaves normally.
219]]
220
221M.oldOsExit = os.exit
222os.exit = function(...)
223  if M.LuaUnit and #M.LuaUnit.instances ~= 0 then
224    local msg = [[You are trying to exit but there is still a running instance of LuaUnit.
225LuaUnit expects to run until the end before exiting with a complete status of successful/failed tests.
226
227To force exit LuaUnit while running, please call before os.exit (assuming lu is the luaunit module loaded):
228
229    lu.unregisterCurrentSuite()
230
231]]
232    M.private.error_fmt(2, msg)
233  end
234  M.oldOsExit(...)
235end
236
237local function pcall_or_abort(func, ...)
238  -- unpack is a global function for Lua 5.1, otherwise use table.unpack
239  local unpack = rawget(_G, "unpack") or table.unpack
240  local result = { pcall(func, ...) }
241  if not result[1] then
242    -- an error occurred
243    print(result[2]) -- error message
244    print()
245    print(M.USAGE)
246    os.exit(-1)
247  end
248  return unpack(result, 2)
249end
250
251local crossTypeOrdering = { number = 1, boolean = 2, string = 3, table = 4, other = 5 }
252local crossTypeComparison = {
253  number = function(a, b)
254    return a < b
255  end,
256  string = function(a, b)
257    return a < b
258  end,
259  other = function(a, b)
260    return tostring(a) < tostring(b)
261  end,
262}
263
264local function crossTypeSort(a, b)
265  local type_a, type_b = type(a), type(b)
266  if type_a == type_b then
267    local func = crossTypeComparison[type_a] or crossTypeComparison.other
268    return func(a, b)
269  end
270  type_a = crossTypeOrdering[type_a] or crossTypeOrdering.other
271  type_b = crossTypeOrdering[type_b] or crossTypeOrdering.other
272  return type_a < type_b
273end
274
275local function __genSortedIndex(t)
276  -- Returns a sequence consisting of t's keys, sorted.
277  local sortedIndex = {}
278
279  for key, _ in pairs(t) do
280    table.insert(sortedIndex, key)
281  end
282
283  table.sort(sortedIndex, crossTypeSort)
284  return sortedIndex
285end
286M.private.__genSortedIndex = __genSortedIndex
287
288local function sortedNext(state, control)
289  -- Equivalent of the next() function of table iteration, but returns the
290  -- keys in sorted order (see __genSortedIndex and crossTypeSort).
291  -- The state is a temporary variable during iteration and contains the
292  -- sorted key table (state.sortedIdx). It also stores the last index (into
293  -- the keys) used by the iteration, to find the next one quickly.
294  local key
295
296  -- print("sortedNext: control = "..tostring(control) )
297  if control == nil then
298    -- start of iteration
299    state.count = #state.sortedIdx
300    state.lastIdx = 1
301    key = state.sortedIdx[1]
302    return key, state.t[key]
303  end
304
305  -- normally, we expect the control variable to match the last key used
306  if control ~= state.sortedIdx[state.lastIdx] then
307    -- strange, we have to find the next value by ourselves
308    -- the key table is sorted in crossTypeSort() order! -> use bisection
309    local lower, upper = 1, state.count
310    repeat
311      state.lastIdx = math.modf((lower + upper) / 2)
312      key = state.sortedIdx[state.lastIdx]
313      if key == control then
314        break -- key found (and thus prev index)
315      end
316      if crossTypeSort(key, control) then
317        -- key < control, continue search "right" (towards upper bound)
318        lower = state.lastIdx + 1
319      else
320        -- key > control, continue search "left" (towards lower bound)
321        upper = state.lastIdx - 1
322      end
323    until lower > upper
324    if lower > upper then -- only true if the key wasn't found, ...
325      state.lastIdx = state.count -- ... so ensure no match in code below
326    end
327  end
328
329  -- proceed by retrieving the next value (or nil) from the sorted keys
330  state.lastIdx = state.lastIdx + 1
331  key = state.sortedIdx[state.lastIdx]
332  if key then
333    return key, state.t[key]
334  end
335
336  -- getting here means returning `nil`, which will end the iteration
337end
338
339local function sortedPairs(tbl)
340  -- Equivalent of the pairs() function on tables. Allows to iterate in
341  -- sorted order. As required by "generic for" loops, this will return the
342  -- iterator (function), an "invariant state", and the initial control value.
343  -- (see http://www.lua.org/pil/7.2.html)
344  return sortedNext, { t = tbl, sortedIdx = __genSortedIndex(tbl) }, nil
345end
346M.private.sortedPairs = sortedPairs
347
348-- seed the random with a strongly varying seed
349math.randomseed(math.floor(os.clock() * 1E11))
350
351local function randomizeTable(t)
352  -- randomize the item orders of the table t
353  for i = #t, 2, -1 do
354    local j = math.random(i)
355    if i ~= j then
356      t[i], t[j] = t[j], t[i]
357    end
358  end
359end
360M.private.randomizeTable = randomizeTable
361
362local function strsplit(delimiter, text)
363  -- Split text into a list consisting of the strings in text, separated
364  -- by strings matching delimiter (which may _NOT_ be a pattern).
365  -- Example: strsplit(", ", "Anna, Bob, Charlie, Dolores")
366  if delimiter == "" or delimiter == nil then -- this would result in endless loops
367    error("delimiter is nil or empty string!")
368  end
369  if text == nil then
370    return nil
371  end
372
373  local list, pos, first, last = {}, 1
374  while true do
375    first, last = text:find(delimiter, pos, true)
376    if first then -- found?
377      table.insert(list, text:sub(pos, first - 1))
378      pos = last + 1
379    else
380      table.insert(list, text:sub(pos))
381      break
382    end
383  end
384  return list
385end
386M.private.strsplit = strsplit
387
388local function hasNewLine(s)
389  -- return true if s has a newline
390  return (string.find(s, "\n", 1, true) ~= nil)
391end
392M.private.hasNewLine = hasNewLine
393
394local function prefixString(prefix, s)
395  -- Prefix all the lines of s with prefix
396  return prefix .. string.gsub(s, "\n", "\n" .. prefix)
397end
398M.private.prefixString = prefixString
399
400local function strMatch(s, pattern, start, final)
401  -- return true if s matches completely the pattern from index start to index end
402  -- return false in every other cases
403  -- if start is nil, matches from the beginning of the string
404  -- if final is nil, matches to the end of the string
405  start = start or 1
406  final = final or string.len(s)
407
408  local foundStart, foundEnd = string.find(s, pattern, start, false)
409  return foundStart == start and foundEnd == final
410end
411M.private.strMatch = strMatch
412
413local function patternFilter(patterns, expr)
414  -- Run `expr` through the inclusion and exclusion rules defined in patterns
415  -- and return true if expr shall be included, false for excluded.
416  -- Inclusion pattern are defined as normal patterns, exclusions
417  -- patterns start with `!` and are followed by a normal pattern
418
419  -- result: nil = UNKNOWN (not matched yet), true = ACCEPT, false = REJECT
420  -- default: true if no explicit "include" is found, set to false otherwise
421  local default, result = true, nil
422
423  if patterns ~= nil then
424    for _, pattern in ipairs(patterns) do
425      local exclude = pattern:sub(1, 1) == "!"
426      if exclude then
427        pattern = pattern:sub(2)
428      else
429        -- at least one include pattern specified, a match is required
430        default = false
431      end
432      -- print('pattern: ',pattern)
433      -- print('exclude: ',exclude)
434      -- print('default: ',default)
435
436      if string.find(expr, pattern) then
437        -- set result to false when excluding, true otherwise
438        result = not exclude
439      end
440    end
441  end
442
443  if result ~= nil then
444    return result
445  end
446  return default
447end
448M.private.patternFilter = patternFilter
449
450local function xmlEscape(s)
451  -- Return s escaped for XML attributes
452  -- escapes table:
453  -- "   &quot;
454  -- '   &apos;
455  -- <   &lt;
456  -- >   &gt;
457  -- &   &amp;
458
459  return string.gsub(s, ".", { ["&"] = "&amp;", ['"'] = "&quot;", ["'"] = "&apos;", ["<"] = "&lt;", [">"] = "&gt;" })
460end
461M.private.xmlEscape = xmlEscape
462
463local function xmlCDataEscape(s)
464  -- Return s escaped for CData section, escapes: "]]>"
465  return string.gsub(s, "]]>", "]]&gt;")
466end
467M.private.xmlCDataEscape = xmlCDataEscape
468
469local function lstrip(s)
470  --[[Return s with all leading white spaces and tabs removed]]
471  local idx = 0
472  while idx < s:len() do
473    idx = idx + 1
474    local c = s:sub(idx, idx)
475    if c ~= " " and c ~= "\t" then
476      break
477    end
478  end
479  return s:sub(idx)
480end
481M.private.lstrip = lstrip
482
483local function extractFileLineInfo(s)
484  --[[ From a string in the form "(leading spaces) dir1/dir2\dir3\file.lua:linenb: msg"
485
486    Return the "file.lua:linenb" information
487    ]]
488  local s2 = lstrip(s)
489  local firstColon = s2:find(":", 1, true)
490  if firstColon == nil then
491    -- string is not in the format file:line:
492    return s
493  end
494  local secondColon = s2:find(":", firstColon + 1, true)
495  if secondColon == nil then
496    -- string is not in the format file:line:
497    return s
498  end
499
500  return s2:sub(1, secondColon - 1)
501end
502M.private.extractFileLineInfo = extractFileLineInfo
503
504local function stripLuaunitTrace2(stackTrace, errMsg)
505  --[[
506    -- Example of  a traceback:
507    <<stack traceback:
508        example_with_luaunit.lua:130: in function 'test2_withFailure'
509        ./luaunit.lua:1449: in function <./luaunit.lua:1449>
510        [C]: in function 'xpcall'
511        ./luaunit.lua:1449: in function 'protectedCall'
512        ./luaunit.lua:1508: in function 'execOneFunction'
513        ./luaunit.lua:1596: in function 'runSuiteByInstances'
514        ./luaunit.lua:1660: in function 'runSuiteByNames'
515        ./luaunit.lua:1736: in function 'runSuite'
516        example_with_luaunit.lua:140: in main chunk
517        [C]: in ?>>
518    error message: <<example_with_luaunit.lua:130: expected 2, got 1>>
519
520        Other example:
521    <<stack traceback:
522        ./luaunit.lua:545: in function 'assertEquals'
523        example_with_luaunit.lua:58: in function 'TestToto.test7'
524        ./luaunit.lua:1517: in function <./luaunit.lua:1517>
525        [C]: in function 'xpcall'
526        ./luaunit.lua:1517: in function 'protectedCall'
527        ./luaunit.lua:1578: in function 'execOneFunction'
528        ./luaunit.lua:1677: in function 'runSuiteByInstances'
529        ./luaunit.lua:1730: in function 'runSuiteByNames'
530        ./luaunit.lua:1806: in function 'runSuite'
531        example_with_luaunit.lua:140: in main chunk
532        [C]: in ?>>
533    error message: <<example_with_luaunit.lua:58:  expected 2, got 1>>
534
535    <<stack traceback:
536        luaunit2/example_with_luaunit.lua:124: in function 'test1_withFailure'
537        luaunit2/luaunit.lua:1532: in function <luaunit2/luaunit.lua:1532>
538        [C]: in function 'xpcall'
539        luaunit2/luaunit.lua:1532: in function 'protectedCall'
540        luaunit2/luaunit.lua:1591: in function 'execOneFunction'
541        luaunit2/luaunit.lua:1679: in function 'runSuiteByInstances'
542        luaunit2/luaunit.lua:1743: in function 'runSuiteByNames'
543        luaunit2/luaunit.lua:1819: in function 'runSuite'
544        luaunit2/example_with_luaunit.lua:140: in main chunk
545        [C]: in ?>>
546    error message: <<luaunit2/example_with_luaunit.lua:124:  expected 2, got 1>>
547
548
549    -- first line is "stack traceback": KEEP
550    -- next line may be luaunit line: REMOVE
551    -- next lines are call in the program under testOk: REMOVE
552    -- next lines are calls from luaunit to call the program under test: KEEP
553
554    -- Strategy:
555    -- keep first line
556    -- remove lines that are part of luaunit
557    -- kepp lines until we hit a luaunit line
558
559    The strategy for stripping is:
560    * keep first line "stack traceback:"
561    * part1:
562        * analyse all lines of the stack from bottom to top of the stack (first line to last line)
563        * extract the "file:line:" part of the line
564        * compare it with the "file:line" part of the error message
565        * if it does not match strip the line
566        * if it matches, keep the line and move to part 2
567    * part2:
568        * anything NOT starting with luaunit.lua is the interesting part of the stack trace
569        * anything starting again with luaunit.lua is part of the test launcher and should be stripped out
570    ]]
571
572  local function isLuaunitInternalLine(s)
573    -- return true if line of stack trace comes from inside luaunit
574    return s:find("[/\\]luaunit%.lua:%d+: ") ~= nil
575  end
576
577  -- print( '<<'..stackTrace..'>>' )
578
579  local t = strsplit("\n", stackTrace)
580  -- print( prettystr(t) )
581
582  local idx = 2
583
584  local errMsgFileLine = extractFileLineInfo(errMsg)
585  -- print('emfi="'..errMsgFileLine..'"')
586
587  -- remove lines that are still part of luaunit
588  while t[idx] and extractFileLineInfo(t[idx]) ~= errMsgFileLine do
589    -- print('Removing : '..t[idx] )
590    table.remove(t, idx)
591  end
592
593  -- keep lines until we hit luaunit again
594  while t[idx] and (not isLuaunitInternalLine(t[idx])) do
595    -- print('Keeping : '..t[idx] )
596    idx = idx + 1
597  end
598
599  -- remove remaining luaunit lines
600  while t[idx] do
601    -- print('Removing2 : '..t[idx] )
602    table.remove(t, idx)
603  end
604
605  -- print( prettystr(t) )
606  return table.concat(t, "\n")
607end
608M.private.stripLuaunitTrace2 = stripLuaunitTrace2
609
610local function prettystr_sub(v, indentLevel, printTableRefs, cycleDetectTable)
611  local type_v = type(v)
612  if "string" == type_v then
613    -- use clever delimiters according to content:
614    -- enclose with single quotes if string contains ", but no '
615    if v:find('"', 1, true) and not v:find("'", 1, true) then
616      return "'" .. v .. "'"
617    end
618    -- use double quotes otherwise, escape embedded "
619    return '"' .. v:gsub('"', '\\"') .. '"'
620  elseif "table" == type_v then
621    -- if v.__class__ then
622    --    return string.gsub( tostring(v), 'table', v.__class__ )
623    -- end
624    return M.private._table_tostring(v, indentLevel, printTableRefs, cycleDetectTable)
625  elseif "number" == type_v then
626    -- eliminate differences in formatting between various Lua versions
627    if v ~= v then
628      return "#NaN" -- "not a number"
629    end
630    if v == math.huge then
631      return "#Inf" -- "infinite"
632    end
633    if v == -math.huge then
634      return "-#Inf"
635    end
636    if _VERSION == "Lua 5.3" then
637      local i = math.tointeger(v)
638      if i then
639        return tostring(i)
640      end
641    end
642  end
643
644  return tostring(v)
645end
646
647local function prettystr(v)
648  --[[ Pretty string conversion, to display the full content of a variable of any type.
649
650    * string are enclosed with " by default, or with ' if string contains a "
651    * tables are expanded to show their full content, with indentation in case of nested tables
652    ]]
653  --
654  local cycleDetectTable = {}
655  local s = prettystr_sub(v, 1, M.PRINT_TABLE_REF_IN_ERROR_MSG, cycleDetectTable)
656  if cycleDetectTable.detected and not M.PRINT_TABLE_REF_IN_ERROR_MSG then
657    -- some table contain recursive references,
658    -- so we must recompute the value by including all table references
659    -- else the result looks like crap
660    cycleDetectTable = {}
661    s = prettystr_sub(v, 1, true, cycleDetectTable)
662  end
663  return s
664end
665M.prettystr = prettystr
666
667function M.adjust_err_msg_with_iter(err_msg, iter_msg)
668  --[[ Adjust the error message err_msg: trim the FAILURE_PREFIX or SUCCESS_PREFIX information if needed,
669    add the iteration message if any and return the result.
670
671    err_msg:  string, error message captured with pcall
672    iter_msg: a string describing the current iteration ("iteration N") or nil
673              if there is no iteration in this test.
674
675    Returns: (new_err_msg, test_status)
676        new_err_msg: string, adjusted error message, or nil in case of success
677        test_status: M.NodeStatus.FAIL, SUCCESS or ERROR according to the information
678                     contained in the error message.
679    ]]
680  if iter_msg then
681    iter_msg = iter_msg .. ", "
682  else
683    iter_msg = ""
684  end
685
686  local RE_FILE_LINE = ".*:%d+: "
687
688  -- error message is not necessarily a string,
689  -- so convert the value to string with prettystr()
690  if type(err_msg) ~= "string" then
691    err_msg = prettystr(err_msg)
692  end
693
694  if (err_msg:find(M.SUCCESS_PREFIX) == 1) or err_msg:match("(" .. RE_FILE_LINE .. ")" .. M.SUCCESS_PREFIX .. ".*") then
695    -- test finished early with success()
696    return nil, M.NodeStatus.SUCCESS
697  end
698
699  if
700    (err_msg:find(M.SKIP_PREFIX) == 1) or (err_msg:match("(" .. RE_FILE_LINE .. ")" .. M.SKIP_PREFIX .. ".*") ~= nil)
701  then
702    -- substitute prefix by iteration message
703    err_msg = err_msg:gsub(".*" .. M.SKIP_PREFIX, iter_msg, 1)
704    -- print("failure detected")
705    return err_msg, M.NodeStatus.SKIP
706  end
707
708  if
709    (err_msg:find(M.FAILURE_PREFIX) == 1)
710    or (err_msg:match("(" .. RE_FILE_LINE .. ")" .. M.FAILURE_PREFIX .. ".*") ~= nil)
711  then
712    -- substitute prefix by iteration message
713    err_msg = err_msg:gsub(M.FAILURE_PREFIX, iter_msg, 1)
714    -- print("failure detected")
715    return err_msg, M.NodeStatus.FAIL
716  end
717
718  -- print("error detected")
719  -- regular error, not a failure
720  if iter_msg then
721    local match
722    -- "./test\\test_luaunit.lua:2241: some error msg
723    match = err_msg:match("(.*:%d+: ).*")
724    if match then
725      err_msg = err_msg:gsub(match, match .. iter_msg)
726    else
727      -- no file:line: infromation, just add the iteration info at the beginning of the line
728      err_msg = iter_msg .. err_msg
729    end
730  end
731  return err_msg, M.NodeStatus.ERROR
732end
733
734local function tryMismatchFormatting(table_a, table_b, doDeepAnalysis, margin)
735  --[[
736    Prepares a nice error message when comparing tables, performing a deeper
737    analysis.
738
739    Arguments:
740    * table_a, table_b: tables to be compared
741    * doDeepAnalysis:
742        M.DEFAULT_DEEP_ANALYSIS: (the default if not specified) perform deep analysis only for big lists and big dictionnaries
743        M.FORCE_DEEP_ANALYSIS  : always perform deep analysis
744        M.DISABLE_DEEP_ANALYSIS: never perform deep analysis
745    * margin: supplied only for almost equality
746
747    Returns: {success, result}
748    * success: false if deep analysis could not be performed
749               in this case, just use standard assertion message
750    * result: if success is true, a multi-line string with deep analysis of the two lists
751    ]]
752
753  -- check if table_a & table_b are suitable for deep analysis
754  if type(table_a) ~= "table" or type(table_b) ~= "table" then
755    return false
756  end
757
758  if doDeepAnalysis == M.DISABLE_DEEP_ANALYSIS then
759    return false
760  end
761
762  local len_a, len_b, isPureList = #table_a, #table_b, true
763
764  for k1, v1 in pairs(table_a) do
765    if type(k1) ~= "number" or k1 > len_a then
766      -- this table a mapping
767      isPureList = false
768      break
769    end
770  end
771
772  if isPureList then
773    for k2, v2 in pairs(table_b) do
774      if type(k2) ~= "number" or k2 > len_b then
775        -- this table a mapping
776        isPureList = false
777        break
778      end
779    end
780  end
781
782  if isPureList and math.min(len_a, len_b) < M.LIST_DIFF_ANALYSIS_THRESHOLD then
783    if not (doDeepAnalysis == M.FORCE_DEEP_ANALYSIS) then
784      return false
785    end
786  end
787
788  if isPureList then
789    return M.private.mismatchFormattingPureList(table_a, table_b, margin)
790  else
791    -- only work on mapping for the moment
792    -- return M.private.mismatchFormattingMapping( table_a, table_b, doDeepAnalysis )
793    return false
794  end
795end
796M.private.tryMismatchFormatting = tryMismatchFormatting
797
798local function getTaTbDescr()
799  if not M.ORDER_ACTUAL_EXPECTED then
800    return "expected", "actual"
801  end
802  return "actual", "expected"
803end
804
805local function extendWithStrFmt(res, ...)
806  table.insert(res, string.format(...))
807end
808
809local function mismatchFormattingMapping(table_a, table_b, doDeepAnalysis)
810  --[[
811    Prepares a nice error message when comparing tables which are not pure lists, performing a deeper
812    analysis.
813
814    Returns: {success, result}
815    * success: false if deep analysis could not be performed
816               in this case, just use standard assertion message
817    * result: if success is true, a multi-line string with deep analysis of the two lists
818    ]]
819
820  -- disable for the moment
821  --[[
822    local result = {}
823    local descrTa, descrTb = getTaTbDescr()
824
825    local keysCommon = {}
826    local keysOnlyTa = {}
827    local keysOnlyTb = {}
828    local keysDiffTaTb = {}
829
830    local k, v
831
832    for k,v in pairs( table_a ) do
833        if is_equal( v, table_b[k] ) then
834            table.insert( keysCommon, k )
835        else
836            if table_b[k] == nil then
837                table.insert( keysOnlyTa, k )
838            else
839                table.insert( keysDiffTaTb, k )
840            end
841        end
842    end
843
844    for k,v in pairs( table_b ) do
845        if not is_equal( v, table_a[k] ) and table_a[k] == nil then
846            table.insert( keysOnlyTb, k )
847        end
848    end
849
850    local len_a = #keysCommon + #keysDiffTaTb + #keysOnlyTa
851    local len_b = #keysCommon + #keysDiffTaTb + #keysOnlyTb
852    local limited_display = (len_a < 5 or len_b < 5)
853
854    if math.min(len_a, len_b) < M.TABLE_DIFF_ANALYSIS_THRESHOLD then
855        return false
856    end
857
858    if not limited_display then
859        if len_a == len_b then
860            extendWithStrFmt( result, 'Table A (%s) and B (%s) both have %d items', descrTa, descrTb, len_a )
861        else
862            extendWithStrFmt( result, 'Table A (%s) has %d items and table B (%s) has %d items', descrTa, len_a, descrTb, len_b )
863            end
864
865        if #keysCommon == 0 and #keysDiffTaTb == 0 then
866            table.insert( result, 'Table A and B have no keys in common, they are totally different')
867        else
868            local s_other = 'other '
869            if #keysCommon then
870                extendWithStrFmt( result, 'Table A and B have %d identical items', #keysCommon )
871            else
872                table.insert( result, 'Table A and B have no identical items' )
873                s_other = ''
874            end
875
876            if #keysDiffTaTb ~= 0 then
877                result[#result] = string.format( '%s and %d items differing present in both tables', result[#result], #keysDiffTaTb)
878            else
879                result[#result] = string.format( '%s and no %sitems differing present in both tables', result[#result], s_other, #keysDiffTaTb)
880            end
881        end
882
883        extendWithStrFmt( result, 'Table A has %d keys not present in table B and table B has %d keys not present in table A', #keysOnlyTa, #keysOnlyTb )
884    end
885
886    local function keytostring(k)
887        if "string" == type(k) and k:match("^[_%a][_%w]*$") then
888            return k
889        end
890        return prettystr(k)
891    end
892
893    if #keysDiffTaTb ~= 0 then
894        table.insert( result, 'Items differing in A and B:')
895        for k,v in sortedPairs( keysDiffTaTb ) do
896            extendWithStrFmt( result, '  - A[%s]: %s', keytostring(v), prettystr(table_a[v]) )
897            extendWithStrFmt( result, '  + B[%s]: %s', keytostring(v), prettystr(table_b[v]) )
898        end
899    end
900
901    if #keysOnlyTa ~= 0 then
902        table.insert( result, 'Items only in table A:' )
903        for k,v in sortedPairs( keysOnlyTa ) do
904            extendWithStrFmt( result, '  - A[%s]: %s', keytostring(v), prettystr(table_a[v]) )
905        end
906    end
907
908    if #keysOnlyTb ~= 0 then
909        table.insert( result, 'Items only in table B:' )
910        for k,v in sortedPairs( keysOnlyTb ) do
911            extendWithStrFmt( result, '  + B[%s]: %s', keytostring(v), prettystr(table_b[v]) )
912        end
913    end
914
915    if #keysCommon ~= 0 then
916        table.insert( result, 'Items common to A and B:')
917        for k,v in sortedPairs( keysCommon ) do
918            extendWithStrFmt( result, '  = A and B [%s]: %s', keytostring(v), prettystr(table_a[v]) )
919        end
920    end
921
922    return true, table.concat( result, '\n')
923    ]]
924end
925M.private.mismatchFormattingMapping = mismatchFormattingMapping
926
927local function mismatchFormattingPureList(table_a, table_b, margin)
928  --[[
929    Prepares a nice error message when comparing tables which are lists, performing a deeper
930    analysis.
931
932    margin is supplied only for almost equality
933
934    Returns: {success, result}
935    * success: false if deep analysis could not be performed
936               in this case, just use standard assertion message
937    * result: if success is true, a multi-line string with deep analysis of the two lists
938    ]]
939  local result, descrTa, descrTb = {}, getTaTbDescr()
940
941  local len_a, len_b, refa, refb = #table_a, #table_b, "", ""
942  if M.PRINT_TABLE_REF_IN_ERROR_MSG then
943    refa, refb =
944      string.format("<%s> ", M.private.table_ref(table_a)), string.format("<%s> ", M.private.table_ref(table_b))
945  end
946  local longest, shortest = math.max(len_a, len_b), math.min(len_a, len_b)
947  local deltalv = longest - shortest
948
949  local commonUntil = shortest
950  for i = 1, shortest do
951    if not M.private.is_table_equals(table_a[i], table_b[i], margin) then
952      commonUntil = i - 1
953      break
954    end
955  end
956
957  local commonBackTo = shortest - 1
958  for i = 0, shortest - 1 do
959    if not M.private.is_table_equals(table_a[len_a - i], table_b[len_b - i], margin) then
960      commonBackTo = i - 1
961      break
962    end
963  end
964
965  table.insert(result, "List difference analysis:")
966  if len_a == len_b then
967    -- TODO: handle expected/actual naming
968    extendWithStrFmt(result, "* lists %sA (%s) and %sB (%s) have the same size", refa, descrTa, refb, descrTb)
969  else
970    extendWithStrFmt(
971      result,
972      "* list sizes differ: list %sA (%s) has %d items, list %sB (%s) has %d items",
973      refa,
974      descrTa,
975      len_a,
976      refb,
977      descrTb,
978      len_b
979    )
980  end
981
982  extendWithStrFmt(result, "* lists A and B start differing at index %d", commonUntil + 1)
983  if commonBackTo >= 0 then
984    if deltalv > 0 then
985      extendWithStrFmt(
986        result,
987        "* lists A and B are equal again from index %d for A, %d for B",
988        len_a - commonBackTo,
989        len_b - commonBackTo
990      )
991    else
992      extendWithStrFmt(result, "* lists A and B are equal again from index %d", len_a - commonBackTo)
993    end
994  end
995
996  local function insertABValue(ai, bi)
997    bi = bi or ai
998    if M.private.is_table_equals(table_a[ai], table_b[bi], margin) then
999      return extendWithStrFmt(result, "  = A[%d], B[%d]: %s", ai, bi, prettystr(table_a[ai]))
1000    else
1001      extendWithStrFmt(result, "  - A[%d]: %s", ai, prettystr(table_a[ai]))
1002      extendWithStrFmt(result, "  + B[%d]: %s", bi, prettystr(table_b[bi]))
1003    end
1004  end
1005
1006  -- common parts to list A & B, at the beginning
1007  if commonUntil > 0 then
1008    table.insert(result, "* Common parts:")
1009    for i = 1, commonUntil do
1010      insertABValue(i)
1011    end
1012  end
1013
1014  -- diffing parts to list A & B
1015  if commonUntil < shortest - commonBackTo - 1 then
1016    table.insert(result, "* Differing parts:")
1017    for i = commonUntil + 1, shortest - commonBackTo - 1 do
1018      insertABValue(i)
1019    end
1020  end
1021
1022  -- display indexes of one list, with no match on other list
1023  if shortest - commonBackTo <= longest - commonBackTo - 1 then
1024    table.insert(result, "* Present only in one list:")
1025    for i = shortest - commonBackTo, longest - commonBackTo - 1 do
1026      if len_a > len_b then
1027        extendWithStrFmt(result, "  - A[%d]: %s", i, prettystr(table_a[i]))
1028        -- table.insert( result, '+ (no matching B index)')
1029      else
1030        -- table.insert( result, '- no matching A index')
1031        extendWithStrFmt(result, "  + B[%d]: %s", i, prettystr(table_b[i]))
1032      end
1033    end
1034  end
1035
1036  -- common parts to list A & B, at the end
1037  if commonBackTo >= 0 then
1038    table.insert(result, "* Common parts at the end of the lists")
1039    for i = longest - commonBackTo, longest do
1040      if len_a > len_b then
1041        insertABValue(i, i - deltalv)
1042      else
1043        insertABValue(i - deltalv, i)
1044      end
1045    end
1046  end
1047
1048  return true, table.concat(result, "\n")
1049end
1050M.private.mismatchFormattingPureList = mismatchFormattingPureList
1051
1052local function prettystrPairs(value1, value2, suffix_a, suffix_b)
1053  --[[
1054    This function helps with the recurring task of constructing the "expected
1055    vs. actual" error messages. It takes two arbitrary values and formats
1056    corresponding strings with prettystr().
1057
1058    To keep the (possibly complex) output more readable in case the resulting
1059    strings contain line breaks, they get automatically prefixed with additional
1060    newlines. Both suffixes are optional (default to empty strings), and get
1061    appended to the "value1" string. "suffix_a" is used if line breaks were
1062    encountered, "suffix_b" otherwise.
1063
1064    Returns the two formatted strings (including padding/newlines).
1065    ]]
1066  local str1, str2 = prettystr(value1), prettystr(value2)
1067  if hasNewLine(str1) or hasNewLine(str2) then
1068    -- line break(s) detected, add padding
1069    return "\n" .. str1 .. (suffix_a or ""), "\n" .. str2
1070  end
1071  return str1 .. (suffix_b or ""), str2
1072end
1073M.private.prettystrPairs = prettystrPairs
1074
1075local UNKNOWN_REF = "table 00-unknown ref"
1076local ref_generator = { value = 1, [UNKNOWN_REF] = 0 }
1077
1078local function table_ref(t)
1079  -- return the default tostring() for tables, with the table ID, even if the table has a metatable
1080  -- with the __tostring converter
1081  local ref = ""
1082  local mt = getmetatable(t)
1083  if mt == nil then
1084    ref = tostring(t)
1085  else
1086    local success, result
1087    success, result = pcall(setmetatable, t, nil)
1088    if not success then
1089      -- protected table, if __tostring is defined, we can
1090      -- not get the reference. And we can not know in advance.
1091      ref = tostring(t)
1092      if not ref:match("table: 0?x?[%x]+") then
1093        return UNKNOWN_REF
1094      end
1095    else
1096      ref = tostring(t)
1097      setmetatable(t, mt)
1098    end
1099  end
1100  -- strip the "table: " part
1101  ref = ref:sub(8)
1102  if ref ~= UNKNOWN_REF and ref_generator[ref] == nil then
1103    -- Create a new reference number
1104    ref_generator[ref] = ref_generator.value
1105    ref_generator.value = ref_generator.value + 1
1106  end
1107  if M.PRINT_TABLE_REF_IN_ERROR_MSG then
1108    return string.format("table %02d-%s", ref_generator[ref], ref)
1109  else
1110    return string.format("table %02d", ref_generator[ref])
1111  end
1112end
1113M.private.table_ref = table_ref
1114
1115local TABLE_TOSTRING_SEP = ", "
1116local TABLE_TOSTRING_SEP_LEN = string.len(TABLE_TOSTRING_SEP)
1117
1118local function _table_tostring(tbl, indentLevel, printTableRefs, cycleDetectTable)
1119  printTableRefs = printTableRefs or M.PRINT_TABLE_REF_IN_ERROR_MSG
1120  cycleDetectTable = cycleDetectTable or {}
1121  cycleDetectTable[tbl] = true
1122
1123  local result, dispOnMultLines = {}, false
1124
1125  -- like prettystr but do not enclose with "" if the string is just alphanumerical
1126  -- this is better for displaying table keys who are often simple strings
1127  local function keytostring(k)
1128    if "string" == type(k) and k:match("^[_%a][_%w]*$") then
1129      return k
1130    end
1131    return prettystr_sub(k, indentLevel + 1, printTableRefs, cycleDetectTable)
1132  end
1133
1134  local mt = getmetatable(tbl)
1135
1136  if mt and mt.__tostring then
1137    -- if table has a __tostring() function in its metatable, use it to display the table
1138    -- else, compute a regular table
1139    result = tostring(tbl)
1140    if type(result) ~= "string" then
1141      return string.format('<invalid tostring() result: "%s" >', prettystr(result))
1142    end
1143    result = strsplit("\n", result)
1144    return M.private._table_tostring_format_multiline_string(result, indentLevel)
1145  else
1146    -- no metatable, compute the table representation
1147
1148    local entry, count, seq_index = nil, 0, 1
1149    for k, v in sortedPairs(tbl) do
1150      -- key part
1151      if k == seq_index then
1152        -- for the sequential part of tables, we'll skip the "<key>=" output
1153        entry = ""
1154        seq_index = seq_index + 1
1155      elseif cycleDetectTable[k] then
1156        -- recursion in the key detected
1157        cycleDetectTable.detected = true
1158        entry = "<" .. table_ref(k) .. ">="
1159      else
1160        entry = keytostring(k) .. "="
1161      end
1162
1163      -- value part
1164      if cycleDetectTable[v] then
1165        -- recursion in the value detected!
1166        cycleDetectTable.detected = true
1167        entry = entry .. "<" .. table_ref(v) .. ">"
1168      else
1169        entry = entry .. prettystr_sub(v, indentLevel + 1, printTableRefs, cycleDetectTable)
1170      end
1171      count = count + 1
1172      result[count] = entry
1173    end
1174    return M.private._table_tostring_format_result(tbl, result, indentLevel, printTableRefs)
1175  end
1176end
1177M.private._table_tostring = _table_tostring -- prettystr_sub() needs it
1178
1179local function _table_tostring_format_multiline_string(tbl_str, indentLevel)
1180  local indentString = "\n" .. string.rep("    ", indentLevel - 1)
1181  return table.concat(tbl_str, indentString)
1182end
1183M.private._table_tostring_format_multiline_string = _table_tostring_format_multiline_string
1184
1185local function _table_tostring_format_result(tbl, result, indentLevel, printTableRefs)
1186  -- final function called in _table_to_string() to format the resulting list of
1187  -- string describing the table.
1188
1189  local dispOnMultLines = false
1190
1191  -- set dispOnMultLines to true if the maximum LINE_LENGTH would be exceeded with the values
1192  local totalLength = 0
1193  for k, v in ipairs(result) do
1194    totalLength = totalLength + string.len(v)
1195    if totalLength >= M.LINE_LENGTH then
1196      dispOnMultLines = true
1197      break
1198    end
1199  end
1200
1201  -- set dispOnMultLines to true if the max LINE_LENGTH would be exceeded
1202  -- with the values and the separators.
1203  if not dispOnMultLines then
1204    -- adjust with length of separator(s):
1205    -- two items need 1 sep, three items two seps, ... plus len of '{}'
1206    if #result > 0 then
1207      totalLength = totalLength + TABLE_TOSTRING_SEP_LEN * (#result - 1)
1208    end
1209    dispOnMultLines = (totalLength + 2 >= M.LINE_LENGTH)
1210  end
1211
1212  -- now reformat the result table (currently holding element strings)
1213  if dispOnMultLines then
1214    local indentString = string.rep("    ", indentLevel - 1)
1215    result = { "{\n    ", indentString, table.concat(result, ",\n    " .. indentString), "\n", indentString, "}" }
1216  else
1217    result = { "{", table.concat(result, TABLE_TOSTRING_SEP), "}" }
1218  end
1219  if printTableRefs then
1220    table.insert(result, 1, "<" .. table_ref(tbl) .. "> ") -- prepend table ref
1221  end
1222  return table.concat(result)
1223end
1224M.private._table_tostring_format_result = _table_tostring_format_result -- prettystr_sub() needs it
1225
1226local function table_findkeyof(t, element)
1227  -- Return the key k of the given element in table t, so that t[k] == element
1228  -- (or `nil` if element is not present within t). Note that we use our
1229  -- 'general' is_equal comparison for matching, so this function should
1230  -- handle table-type elements gracefully and consistently.
1231  if type(t) == "table" then
1232    for k, v in pairs(t) do
1233      if M.private.is_table_equals(v, element) then
1234        return k
1235      end
1236    end
1237  end
1238  return nil
1239end
1240
1241local function _is_table_items_equals(actual, expected)
1242  local type_a, type_e = type(actual), type(expected)
1243
1244  if type_a ~= type_e then
1245    return false
1246  elseif
1247    type_a == "table" --[[and (type_e == 'table')]]
1248  then
1249    for k, v in pairs(actual) do
1250      if table_findkeyof(expected, v) == nil then
1251        return false -- v not contained in expected
1252      end
1253    end
1254    for k, v in pairs(expected) do
1255      if table_findkeyof(actual, v) == nil then
1256        return false -- v not contained in actual
1257      end
1258    end
1259    return true
1260  elseif actual ~= expected then
1261    return false
1262  end
1263
1264  return true
1265end
1266
1267--[[
1268This is a specialized metatable to help with the bookkeeping of recursions
1269in _is_table_equals(). It provides an __index table that implements utility
1270functions for easier management of the table. The "cached" method queries
1271the state of a specific (actual,expected) pair; and the "store" method sets
1272this state to the given value. The state of pairs not "seen" / visited is
1273assumed to be `nil`.
1274]]
1275local _recursion_cache_MT = {
1276  __index = {
1277    -- Return the cached value for an (actual,expected) pair (or `nil`)
1278    cached = function(t, actual, expected)
1279      local subtable = t[actual] or {}
1280      return subtable[expected]
1281    end,
1282
1283    -- Store cached value for a specific (actual,expected) pair.
1284    -- Returns the value, so it's easy to use for a "tailcall" (return ...).
1285    store = function(t, actual, expected, value, asymmetric)
1286      local subtable = t[actual]
1287      if not subtable then
1288        subtable = {}
1289        t[actual] = subtable
1290      end
1291      subtable[expected] = value
1292
1293      -- Unless explicitly marked "asymmetric": Consider the recursion
1294      -- on (expected,actual) to be equivalent to (actual,expected) by
1295      -- default, and thus cache the value for both.
1296      if not asymmetric then
1297        t:store(expected, actual, value, true)
1298      end
1299
1300      return value
1301    end,
1302  },
1303}
1304
1305local function _is_table_equals(actual, expected, cycleDetectTable, marginForAlmostEqual)
1306  --[[Returns true if both table are equal.
1307
1308    If argument marginForAlmostEqual is suppied, number comparison is done using alomstEqual instead
1309    of strict equality.
1310
1311    cycleDetectTable is an internal argument used during recursion on tables.
1312    ]]
1313  -- print('_is_table_equals( \n     '..prettystr(actual)..'\n      , '..prettystr(expected)..
1314  --                        '\n     , '..prettystr(cycleDetectTable)..'\n    , '..prettystr(marginForAlmostEqual)..' )')
1315
1316  local type_a, type_e = type(actual), type(expected)
1317
1318  if type_a ~= type_e then
1319    return false -- different types won't match
1320  end
1321
1322  if type_a == "number" then
1323    if marginForAlmostEqual ~= nil then
1324      return M.almostEquals(actual, expected, marginForAlmostEqual)
1325    else
1326      return actual == expected
1327    end
1328  elseif type_a ~= "table" then
1329    -- other types compare directly
1330    return actual == expected
1331  end
1332
1333  cycleDetectTable = cycleDetectTable or { actual = {}, expected = {} }
1334  if cycleDetectTable.actual[actual] then
1335    -- oh, we hit a cycle in actual
1336    if cycleDetectTable.expected[expected] then
1337      -- uh, we hit a cycle at the same time in expected
1338      -- so the two tables have similar structure
1339      return true
1340    end
1341
1342    -- cycle was hit only in actual, the structure differs from expected
1343    return false
1344  end
1345
1346  if cycleDetectTable.expected[expected] then
1347    -- no cycle in actual, but cycle in expected
1348    -- the structure differ
1349    return false
1350  end
1351
1352  -- at this point, no table cycle detected, we are
1353  -- seeing this table for the first time
1354
1355  -- mark the cycle detection
1356  cycleDetectTable.actual[actual] = true
1357  cycleDetectTable.expected[expected] = true
1358
1359  local actualKeysMatched = {}
1360  for k, v in pairs(actual) do
1361    actualKeysMatched[k] = true -- Keep track of matched keys
1362    if not _is_table_equals(v, expected[k], cycleDetectTable, marginForAlmostEqual) then
1363      -- table differs on this key
1364      -- clear the cycle detection before returning
1365      cycleDetectTable.actual[actual] = nil
1366      cycleDetectTable.expected[expected] = nil
1367      return false
1368    end
1369  end
1370
1371  for k, v in pairs(expected) do
1372    if not actualKeysMatched[k] then
1373      -- Found a key that we did not see in "actual" -> mismatch
1374      -- clear the cycle detection before returning
1375      cycleDetectTable.actual[actual] = nil
1376      cycleDetectTable.expected[expected] = nil
1377      return false
1378    end
1379    -- Otherwise actual[k] was already matched against v = expected[k].
1380  end
1381
1382  -- all key match, we have a match !
1383  cycleDetectTable.actual[actual] = nil
1384  cycleDetectTable.expected[expected] = nil
1385  return true
1386end
1387M.private._is_table_equals = _is_table_equals
1388
1389local function failure(main_msg, extra_msg_or_nil, level)
1390  -- raise an error indicating a test failure
1391  -- for error() compatibility we adjust "level" here (by +1), to report the
1392  -- calling context
1393  local msg
1394  if type(extra_msg_or_nil) == "string" and extra_msg_or_nil:len() > 0 then
1395    msg = extra_msg_or_nil .. "\n" .. main_msg
1396  else
1397    msg = main_msg
1398  end
1399  error(M.FAILURE_PREFIX .. msg, (level or 1) + 1 + M.STRIP_EXTRA_ENTRIES_IN_STACK_TRACE)
1400end
1401
1402local function is_table_equals(actual, expected, marginForAlmostEqual)
1403  return _is_table_equals(actual, expected, nil, marginForAlmostEqual)
1404end
1405M.private.is_table_equals = is_table_equals
1406
1407local function fail_fmt(level, extra_msg_or_nil, ...)
1408  -- failure with printf-style formatted message and given error level
1409  failure(string.format(...), extra_msg_or_nil, (level or 1) + 1)
1410end
1411M.private.fail_fmt = fail_fmt
1412
1413local function error_fmt(level, ...)
1414  -- printf-style error()
1415  error(string.format(...), (level or 1) + 1 + M.STRIP_EXTRA_ENTRIES_IN_STACK_TRACE)
1416end
1417M.private.error_fmt = error_fmt
1418
1419----------------------------------------------------------------
1420--
1421--                     assertions
1422--
1423----------------------------------------------------------------
1424
1425local function errorMsgEquality(actual, expected, doDeepAnalysis, margin)
1426  -- margin is supplied only for almost equal verification
1427
1428  if not M.ORDER_ACTUAL_EXPECTED then
1429    expected, actual = actual, expected
1430  end
1431  if type(expected) == "string" or type(expected) == "table" then
1432    local strExpected, strActual = prettystrPairs(expected, actual)
1433    local result = string.format("expected: %s\nactual: %s", strExpected, strActual)
1434    if margin then
1435      result = result .. "\nwere not equal by the margin of: " .. prettystr(margin)
1436    end
1437
1438    -- extend with mismatch analysis if possible:
1439    local success, mismatchResult
1440    success, mismatchResult = tryMismatchFormatting(actual, expected, doDeepAnalysis, margin)
1441    if success then
1442      result = table.concat({ result, mismatchResult }, "\n")
1443    end
1444    return result
1445  end
1446  return string.format("expected: %s, actual: %s", prettystr(expected), prettystr(actual))
1447end
1448
1449function M.assertError(f, ...)
1450  -- assert that calling f with the arguments will raise an error
1451  -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error
1452  if pcall(f, ...) then
1453    failure("Expected an error when calling function but no error generated", nil, 2)
1454  end
1455end
1456
1457function M.fail(msg)
1458  -- stops a test due to a failure
1459  failure(msg, nil, 2)
1460end
1461
1462function M.failIf(cond, msg)
1463  -- Fails a test with "msg" if condition is true
1464  if cond then
1465    failure(msg, nil, 2)
1466  end
1467end
1468
1469function M.skip(msg)
1470  -- skip a running test
1471  error_fmt(2, M.SKIP_PREFIX .. msg)
1472end
1473
1474function M.skipIf(cond, msg)
1475  -- skip a running test if condition is met
1476  if cond then
1477    error_fmt(2, M.SKIP_PREFIX .. msg)
1478  end
1479end
1480
1481function M.runOnlyIf(cond, msg)
1482  -- continue a running test if condition is met, else skip it
1483  if not cond then
1484    error_fmt(2, M.SKIP_PREFIX .. prettystr(msg))
1485  end
1486end
1487
1488function M.success()
1489  -- stops a test with a success
1490  error_fmt(2, M.SUCCESS_PREFIX)
1491end
1492
1493function M.successIf(cond)
1494  -- stops a test with a success if condition is met
1495  if cond then
1496    error_fmt(2, M.SUCCESS_PREFIX)
1497  end
1498end
1499
1500------------------------------------------------------------------
1501--                  Equality assertions
1502------------------------------------------------------------------
1503
1504function M.assertEquals(actual, expected, extra_msg_or_nil, doDeepAnalysis)
1505  if type(actual) == "table" and type(expected) == "table" then
1506    if not is_table_equals(actual, expected) then
1507      failure(errorMsgEquality(actual, expected, doDeepAnalysis), extra_msg_or_nil, 2)
1508    end
1509  elseif type(actual) ~= type(expected) then
1510    failure(errorMsgEquality(actual, expected), extra_msg_or_nil, 2)
1511  elseif actual ~= expected then
1512    failure(errorMsgEquality(actual, expected), extra_msg_or_nil, 2)
1513  end
1514end
1515
1516function M.almostEquals(actual, expected, margin)
1517  if type(actual) ~= "number" or type(expected) ~= "number" or type(margin) ~= "number" then
1518    error_fmt(
1519      3,
1520      "almostEquals: must supply only number arguments.\nArguments supplied: %s, %s, %s",
1521      prettystr(actual),
1522      prettystr(expected),
1523      prettystr(margin)
1524    )
1525  end
1526  if margin < 0 then
1527    error_fmt(3, "almostEquals: margin must not be negative, current value is " .. margin)
1528  end
1529  return math.abs(expected - actual) <= margin
1530end
1531
1532function M.assertAlmostEquals(actual, expected, margin, extra_msg_or_nil)
1533  -- check that two floats are close by margin
1534  margin = margin or M.EPS
1535  if type(margin) ~= "number" then
1536    error_fmt(2, "almostEquals: margin must be a number, not %s", prettystr(margin))
1537  end
1538
1539  if type(actual) == "table" and type(expected) == "table" then
1540    -- handle almost equals for table
1541    if not is_table_equals(actual, expected, margin) then
1542      failure(errorMsgEquality(actual, expected, nil, margin), extra_msg_or_nil, 2)
1543    end
1544  elseif type(actual) == "number" and type(expected) == "number" and type(margin) == "number" then
1545    if not M.almostEquals(actual, expected, margin) then
1546      if not M.ORDER_ACTUAL_EXPECTED then
1547        expected, actual = actual, expected
1548      end
1549      local delta = math.abs(actual - expected)
1550      fail_fmt(
1551        2,
1552        extra_msg_or_nil,
1553        "Values are not almost equal\n" .. "Actual: %s, expected: %s, delta %s above margin of %s",
1554        actual,
1555        expected,
1556        delta,
1557        margin
1558      )
1559    end
1560  else
1561    error_fmt(
1562      3,
1563      "almostEquals: must supply only number or table arguments.\nArguments supplied: %s, %s, %s",
1564      prettystr(actual),
1565      prettystr(expected),
1566      prettystr(margin)
1567    )
1568  end
1569end
1570
1571function M.assertNotEquals(actual, expected, extra_msg_or_nil)
1572  if type(actual) ~= type(expected) then
1573    return
1574  end
1575
1576  if type(actual) == "table" and type(expected) == "table" then
1577    if not is_table_equals(actual, expected) then
1578      return
1579    end
1580  elseif actual ~= expected then
1581    return
1582  end
1583  fail_fmt(2, extra_msg_or_nil, "Received the not expected value: %s", prettystr(actual))
1584end
1585
1586function M.assertNotAlmostEquals(actual, expected, margin, extra_msg_or_nil)
1587  -- check that two floats are not close by margin
1588  margin = margin or M.EPS
1589  if M.almostEquals(actual, expected, margin) then
1590    if not M.ORDER_ACTUAL_EXPECTED then
1591      expected, actual = actual, expected
1592    end
1593    local delta = math.abs(actual - expected)
1594    fail_fmt(
1595      2,
1596      extra_msg_or_nil,
1597      "Values are almost equal\nActual: %s, expected: %s" .. ", delta %s below margin of %s",
1598      actual,
1599      expected,
1600      delta,
1601      margin
1602    )
1603  end
1604end
1605
1606function M.assertItemsEquals(actual, expected, extra_msg_or_nil)
1607  -- checks that the items of table expected
1608  -- are contained in table actual. Warning, this function
1609  -- is at least O(n^2)
1610  if not _is_table_items_equals(actual, expected) then
1611    expected, actual = prettystrPairs(expected, actual)
1612    fail_fmt(
1613      2,
1614      extra_msg_or_nil,
1615      "Content of the tables are not identical:\nExpected: %s\nActual: %s",
1616      expected,
1617      actual
1618    )
1619  end
1620end
1621
1622------------------------------------------------------------------
1623--                  String assertion
1624------------------------------------------------------------------
1625
1626function M.assertStrContains(str, sub, isPattern, extra_msg_or_nil)
1627  -- this relies on lua string.find function
1628  -- a string always contains the empty string
1629  -- assert( type(str) == 'string', 'Argument 1 of assertStrContains() should be a string.' ) )
1630  -- assert( type(sub) == 'string', 'Argument 2 of assertStrContains() should be a string.' ) )
1631  if not string.find(str, sub, 1, not isPattern) then
1632    sub, str = prettystrPairs(sub, str, "\n")
1633    fail_fmt(2, extra_msg_or_nil, "Could not find %s %s in string %s", isPattern and "pattern" or "substring", sub, str)
1634  end
1635end
1636
1637function M.assertStrIContains(str, sub, extra_msg_or_nil)
1638  -- this relies on lua string.find function
1639  -- a string always contains the empty string
1640  if not string.find(str:lower(), sub:lower(), 1, true) then
1641    sub, str = prettystrPairs(sub, str, "\n")
1642    fail_fmt(2, extra_msg_or_nil, "Could not find (case insensitively) substring %s in string %s", sub, str)
1643  end
1644end
1645
1646function M.assertNotStrContains(str, sub, isPattern, extra_msg_or_nil)
1647  -- this relies on lua string.find function
1648  -- a string always contains the empty string
1649  if string.find(str, sub, 1, not isPattern) then
1650    sub, str = prettystrPairs(sub, str, "\n")
1651    fail_fmt(
1652      2,
1653      extra_msg_or_nil,
1654      "Found the not expected %s %s in string %s",
1655      isPattern and "pattern" or "substring",
1656      sub,
1657      str
1658    )
1659  end
1660end
1661
1662function M.assertNotStrIContains(str, sub, extra_msg_or_nil)
1663  -- this relies on lua string.find function
1664  -- a string always contains the empty string
1665  if string.find(str:lower(), sub:lower(), 1, true) then
1666    sub, str = prettystrPairs(sub, str, "\n")
1667    fail_fmt(2, extra_msg_or_nil, "Found (case insensitively) the not expected substring %s in string %s", sub, str)
1668  end
1669end
1670
1671function M.assertStrMatches(str, pattern, start, final, extra_msg_or_nil)
1672  -- Verify a full match for the string
1673  if not strMatch(str, pattern, start, final) then
1674    pattern, str = prettystrPairs(pattern, str, "\n")
1675    fail_fmt(2, extra_msg_or_nil, "Could not match pattern %s with string %s", pattern, str)
1676  end
1677end
1678
1679local function _assertErrorMsgEquals(stripFileAndLine, expectedMsg, func, ...)
1680  local no_error, error_msg = pcall(func, ...)
1681  if no_error then
1682    failure("No error generated when calling function but expected error: " .. M.prettystr(expectedMsg), nil, 3)
1683  end
1684  if type(expectedMsg) == "string" and type(error_msg) ~= "string" then
1685    -- table are converted to string automatically
1686    error_msg = tostring(error_msg)
1687  end
1688  local differ = false
1689  if stripFileAndLine then
1690    if error_msg:gsub("^.+:%d+: ", "") ~= expectedMsg then
1691      differ = true
1692    end
1693  else
1694    if error_msg ~= expectedMsg then
1695      local tr = type(error_msg)
1696      local te = type(expectedMsg)
1697      if te == "table" then
1698        if tr ~= "table" then
1699          differ = true
1700        else
1701          local ok = pcall(M.assertItemsEquals, error_msg, expectedMsg)
1702          if not ok then
1703            differ = true
1704          end
1705        end
1706      else
1707        differ = true
1708      end
1709    end
1710  end
1711
1712  if differ then
1713    error_msg, expectedMsg = prettystrPairs(error_msg, expectedMsg)
1714    fail_fmt(3, nil, "Error message expected: %s\nError message received: %s\n", expectedMsg, error_msg)
1715  end
1716end
1717
1718function M.assertErrorMsgEquals(expectedMsg, func, ...)
1719  -- assert that calling f with the arguments will raise an error
1720  -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error
1721  _assertErrorMsgEquals(false, expectedMsg, func, ...)
1722end
1723
1724function M.assertErrorMsgContentEquals(expectedMsg, func, ...)
1725  _assertErrorMsgEquals(true, expectedMsg, func, ...)
1726end
1727
1728function M.assertErrorMsgContains(partialMsg, func, ...)
1729  -- assert that calling f with the arguments will raise an error
1730  -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error
1731  local no_error, error_msg = pcall(func, ...)
1732  if no_error then
1733    failure("No error generated when calling function but expected error containing: " .. prettystr(partialMsg), nil, 2)
1734  end
1735  if type(error_msg) ~= "string" then
1736    error_msg = tostring(error_msg)
1737  end
1738  if not string.find(error_msg, partialMsg, nil, true) then
1739    error_msg, partialMsg = prettystrPairs(error_msg, partialMsg)
1740    fail_fmt(2, nil, "Error message does not contain: %s\nError message received: %s\n", partialMsg, error_msg)
1741  end
1742end
1743
1744function M.assertErrorMsgMatches(expectedMsg, func, ...)
1745  -- assert that calling f with the arguments will raise an error
1746  -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error
1747  local no_error, error_msg = pcall(func, ...)
1748  if no_error then
1749    failure('No error generated when calling function but expected error matching: "' .. expectedMsg .. '"', nil, 2)
1750  end
1751  if type(error_msg) ~= "string" then
1752    error_msg = tostring(error_msg)
1753  end
1754  if not strMatch(error_msg, expectedMsg) then
1755    expectedMsg, error_msg = prettystrPairs(expectedMsg, error_msg)
1756    fail_fmt(2, nil, "Error message does not match pattern: %s\nError message received: %s\n", expectedMsg, error_msg)
1757  end
1758end
1759
1760------------------------------------------------------------------
1761--              Type assertions
1762------------------------------------------------------------------
1763
1764function M.assertEvalToTrue(value, extra_msg_or_nil)
1765  if not value then
1766    failure("expected: a value evaluating to true, actual: " .. prettystr(value), extra_msg_or_nil, 2)
1767  end
1768end
1769
1770function M.assertEvalToFalse(value, extra_msg_or_nil)
1771  if value then
1772    failure("expected: false or nil, actual: " .. prettystr(value), extra_msg_or_nil, 2)
1773  end
1774end
1775
1776function M.assertIsTrue(value, extra_msg_or_nil)
1777  if value ~= true then
1778    failure("expected: true, actual: " .. prettystr(value), extra_msg_or_nil, 2)
1779  end
1780end
1781
1782function M.assertNotIsTrue(value, extra_msg_or_nil)
1783  if value == true then
1784    failure("expected: not true, actual: " .. prettystr(value), extra_msg_or_nil, 2)
1785  end
1786end
1787
1788function M.assertIsFalse(value, extra_msg_or_nil)
1789  if value ~= false then
1790    failure("expected: false, actual: " .. prettystr(value), extra_msg_or_nil, 2)
1791  end
1792end
1793
1794function M.assertNotIsFalse(value, extra_msg_or_nil)
1795  if value == false then
1796    failure("expected: not false, actual: " .. prettystr(value), extra_msg_or_nil, 2)
1797  end
1798end
1799
1800function M.assertIsNil(value, extra_msg_or_nil)
1801  if value ~= nil then
1802    failure("expected: nil, actual: " .. prettystr(value), extra_msg_or_nil, 2)
1803  end
1804end
1805
1806function M.assertNotIsNil(value, extra_msg_or_nil)
1807  if value == nil then
1808    failure("expected: not nil, actual: nil", extra_msg_or_nil, 2)
1809  end
1810end
1811
1812--[[
1813Add type assertion functions to the module table M. Each of these functions
1814takes a single parameter "value", and checks that its Lua type matches the
1815expected string (derived from the function name):
1816
1817M.assertIsXxx(value) -> ensure that type(value) conforms to "xxx"
1818]]
1819for _, funcName in ipairs({
1820  "assertIsNumber",
1821  "assertIsString",
1822  "assertIsTable",
1823  "assertIsBoolean",
1824  "assertIsFunction",
1825  "assertIsUserdata",
1826  "assertIsThread",
1827}) do
1828  local typeExpected = funcName:match("^assertIs([A-Z]%a*)$")
1829  -- Lua type() always returns lowercase, also make sure the match() succeeded
1830  typeExpected = typeExpected and typeExpected:lower()
1831    or error("bad function name '" .. funcName .. "' for type assertion")
1832
1833  M[funcName] = function(value, extra_msg_or_nil)
1834    if type(value) ~= typeExpected then
1835      if type(value) == "nil" then
1836        fail_fmt(
1837          2,
1838          extra_msg_or_nil,
1839          "expected: a %s value, actual: nil",
1840          typeExpected,
1841          type(value),
1842          prettystrPairs(value)
1843        )
1844      else
1845        fail_fmt(
1846          2,
1847          extra_msg_or_nil,
1848          "expected: a %s value, actual: type %s, value %s",
1849          typeExpected,
1850          type(value),
1851          prettystrPairs(value)
1852        )
1853      end
1854    end
1855  end
1856end
1857
1858--[[
1859Add shortcuts for verifying type of a variable, without failure (luaunit v2 compatibility)
1860M.isXxx(value) -> returns true if type(value) conforms to "xxx"
1861]]
1862for _, typeExpected in ipairs({ "Number", "String", "Table", "Boolean", "Function", "Userdata", "Thread", "Nil" }) do
1863  local typeExpectedLower = typeExpected:lower()
1864  local isType = function(value)
1865    return (type(value) == typeExpectedLower)
1866  end
1867  M["is" .. typeExpected] = isType
1868  M["is_" .. typeExpectedLower] = isType
1869end
1870
1871--[[
1872Add non-type assertion functions to the module table M. Each of these functions
1873takes a single parameter "value", and checks that its Lua type differs from the
1874expected string (derived from the function name):
1875
1876M.assertNotIsXxx(value) -> ensure that type(value) is not "xxx"
1877]]
1878for _, funcName in ipairs({
1879  "assertNotIsNumber",
1880  "assertNotIsString",
1881  "assertNotIsTable",
1882  "assertNotIsBoolean",
1883  "assertNotIsFunction",
1884  "assertNotIsUserdata",
1885  "assertNotIsThread",
1886}) do
1887  local typeUnexpected = funcName:match("^assertNotIs([A-Z]%a*)$")
1888  -- Lua type() always returns lowercase, also make sure the match() succeeded
1889  typeUnexpected = typeUnexpected and typeUnexpected:lower()
1890    or error("bad function name '" .. funcName .. "' for type assertion")
1891
1892  M[funcName] = function(value, extra_msg_or_nil)
1893    if type(value) == typeUnexpected then
1894      fail_fmt(2, extra_msg_or_nil, "expected: not a %s type, actual: value %s", typeUnexpected, prettystrPairs(value))
1895    end
1896  end
1897end
1898
1899function M.assertIs(actual, expected, extra_msg_or_nil)
1900  if actual ~= expected then
1901    if not M.ORDER_ACTUAL_EXPECTED then
1902      actual, expected = expected, actual
1903    end
1904    local old_print_table_ref_in_error_msg = M.PRINT_TABLE_REF_IN_ERROR_MSG
1905    M.PRINT_TABLE_REF_IN_ERROR_MSG = true
1906    expected, actual = prettystrPairs(expected, actual, "\n", "")
1907    M.PRINT_TABLE_REF_IN_ERROR_MSG = old_print_table_ref_in_error_msg
1908    fail_fmt(
1909      2,
1910      extra_msg_or_nil,
1911      "expected and actual object should not be different\nExpected: %s\nReceived: %s",
1912      expected,
1913      actual
1914    )
1915  end
1916end
1917
1918function M.assertNotIs(actual, expected, extra_msg_or_nil)
1919  if actual == expected then
1920    local old_print_table_ref_in_error_msg = M.PRINT_TABLE_REF_IN_ERROR_MSG
1921    M.PRINT_TABLE_REF_IN_ERROR_MSG = true
1922    local s_expected
1923    if not M.ORDER_ACTUAL_EXPECTED then
1924      s_expected = prettystrPairs(actual)
1925    else
1926      s_expected = prettystrPairs(expected)
1927    end
1928    M.PRINT_TABLE_REF_IN_ERROR_MSG = old_print_table_ref_in_error_msg
1929    fail_fmt(2, extra_msg_or_nil, "expected and actual object should be different: %s", s_expected)
1930  end
1931end
1932
1933------------------------------------------------------------------
1934--              Scientific assertions
1935------------------------------------------------------------------
1936
1937function M.assertIsNaN(value, extra_msg_or_nil)
1938  if type(value) ~= "number" or value == value then
1939    failure("expected: NaN, actual: " .. prettystr(value), extra_msg_or_nil, 2)
1940  end
1941end
1942
1943function M.assertNotIsNaN(value, extra_msg_or_nil)
1944  if type(value) == "number" and value ~= value then
1945    failure("expected: not NaN, actual: NaN", extra_msg_or_nil, 2)
1946  end
1947end
1948
1949function M.assertIsInf(value, extra_msg_or_nil)
1950  if type(value) ~= "number" or math.abs(value) ~= math.huge then
1951    failure("expected: #Inf, actual: " .. prettystr(value), extra_msg_or_nil, 2)
1952  end
1953end
1954
1955function M.assertIsPlusInf(value, extra_msg_or_nil)
1956  if type(value) ~= "number" or value ~= math.huge then
1957    failure("expected: #Inf, actual: " .. prettystr(value), extra_msg_or_nil, 2)
1958  end
1959end
1960
1961function M.assertIsMinusInf(value, extra_msg_or_nil)
1962  if type(value) ~= "number" or value ~= -math.huge then
1963    failure("expected: -#Inf, actual: " .. prettystr(value), extra_msg_or_nil, 2)
1964  end
1965end
1966
1967function M.assertNotIsPlusInf(value, extra_msg_or_nil)
1968  if type(value) == "number" and value == math.huge then
1969    failure("expected: not #Inf, actual: #Inf", extra_msg_or_nil, 2)
1970  end
1971end
1972
1973function M.assertNotIsMinusInf(value, extra_msg_or_nil)
1974  if type(value) == "number" and value == -math.huge then
1975    failure("expected: not -#Inf, actual: -#Inf", extra_msg_or_nil, 2)
1976  end
1977end
1978
1979function M.assertNotIsInf(value, extra_msg_or_nil)
1980  if type(value) == "number" and math.abs(value) == math.huge then
1981    failure("expected: not infinity, actual: " .. prettystr(value), extra_msg_or_nil, 2)
1982  end
1983end
1984
1985function M.assertIsPlusZero(value, extra_msg_or_nil)
1986  if type(value) ~= "number" or value ~= 0 then
1987    failure("expected: +0.0, actual: " .. prettystr(value), extra_msg_or_nil, 2)
1988  else
1989    if 1 / value == -math.huge then
1990      -- more precise error diagnosis
1991      failure("expected: +0.0, actual: -0.0", extra_msg_or_nil, 2)
1992    else
1993      if 1 / value ~= math.huge then
1994        -- strange, case should have already been covered
1995        failure("expected: +0.0, actual: " .. prettystr(value), extra_msg_or_nil, 2)
1996      end
1997    end
1998  end
1999end
2000
2001function M.assertIsMinusZero(value, extra_msg_or_nil)
2002  if type(value) ~= "number" or value ~= 0 then
2003    failure("expected: -0.0, actual: " .. prettystr(value), extra_msg_or_nil, 2)
2004  else
2005    if 1 / value == math.huge then
2006      -- more precise error diagnosis
2007      failure("expected: -0.0, actual: +0.0", extra_msg_or_nil, 2)
2008    else
2009      if 1 / value ~= -math.huge then
2010        -- strange, case should have already been covered
2011        failure("expected: -0.0, actual: " .. prettystr(value), extra_msg_or_nil, 2)
2012      end
2013    end
2014  end
2015end
2016
2017function M.assertNotIsPlusZero(value, extra_msg_or_nil)
2018  if type(value) == "number" and (1 / value == math.huge) then
2019    failure("expected: not +0.0, actual: +0.0", extra_msg_or_nil, 2)
2020  end
2021end
2022
2023function M.assertNotIsMinusZero(value, extra_msg_or_nil)
2024  if type(value) == "number" and (1 / value == -math.huge) then
2025    failure("expected: not -0.0, actual: -0.0", extra_msg_or_nil, 2)
2026  end
2027end
2028
2029function M.assertTableContains(t, expected, extra_msg_or_nil)
2030  -- checks that table t contains the expected element
2031  if table_findkeyof(t, expected) == nil then
2032    t, expected = prettystrPairs(t, expected)
2033    fail_fmt(2, extra_msg_or_nil, "Table %s does NOT contain the expected element %s", t, expected)
2034  end
2035end
2036
2037function M.assertNotTableContains(t, expected, extra_msg_or_nil)
2038  -- checks that table t doesn't contain the expected element
2039  local k = table_findkeyof(t, expected)
2040  if k ~= nil then
2041    t, expected = prettystrPairs(t, expected)
2042    fail_fmt(
2043      2,
2044      extra_msg_or_nil,
2045      "Table %s DOES contain the unwanted element %s (at key %s)",
2046      t,
2047      expected,
2048      prettystr(k)
2049    )
2050  end
2051end
2052
2053----------------------------------------------------------------
2054--                     Compatibility layer
2055----------------------------------------------------------------
2056
2057-- for compatibility with LuaUnit v2.x
2058function M.wrapFunctions()
2059  -- In LuaUnit version <= 2.1 , this function was necessary to include
2060  -- a test function inside the global test suite. Nowadays, the functions
2061  -- are simply run directly as part of the test discovery process.
2062  -- so just do nothing !
2063  io.stderr:write([[Use of WrapFunctions() is no longer needed.
2064Just prefix your test function names with "test" or "Test" and they
2065will be picked up and run by LuaUnit.
2066]])
2067end
2068
2069local list_of_funcs = {
2070  -- { official function name , alias }
2071
2072  -- general assertions
2073  { "assertEquals", "assert_equals" },
2074  { "assertItemsEquals", "assert_items_equals" },
2075  { "assertNotEquals", "assert_not_equals" },
2076  { "assertAlmostEquals", "assert_almost_equals" },
2077  { "assertNotAlmostEquals", "assert_not_almost_equals" },
2078  { "assertEvalToTrue", "assert_eval_to_true" },
2079  { "assertEvalToFalse", "assert_eval_to_false" },
2080  { "assertStrContains", "assert_str_contains" },
2081  { "assertStrIContains", "assert_str_icontains" },
2082  { "assertNotStrContains", "assert_not_str_contains" },
2083  { "assertNotStrIContains", "assert_not_str_icontains" },
2084  { "assertStrMatches", "assert_str_matches" },
2085  { "assertError", "assert_error" },
2086  { "assertErrorMsgEquals", "assert_error_msg_equals" },
2087  { "assertErrorMsgContains", "assert_error_msg_contains" },
2088  { "assertErrorMsgMatches", "assert_error_msg_matches" },
2089  { "assertErrorMsgContentEquals", "assert_error_msg_content_equals" },
2090  { "assertIs", "assert_is" },
2091  { "assertNotIs", "assert_not_is" },
2092  { "assertTableContains", "assert_table_contains" },
2093  { "assertNotTableContains", "assert_not_table_contains" },
2094  { "wrapFunctions", "WrapFunctions" },
2095  { "wrapFunctions", "wrap_functions" }, -- type assertions: assertIsXXX -> assert_is_xxx
2096  { "assertIsNumber", "assert_is_number" },
2097  { "assertIsString", "assert_is_string" },
2098  { "assertIsTable", "assert_is_table" },
2099  { "assertIsBoolean", "assert_is_boolean" },
2100  { "assertIsNil", "assert_is_nil" },
2101  { "assertIsTrue", "assert_is_true" },
2102  { "assertIsFalse", "assert_is_false" },
2103  { "assertIsNaN", "assert_is_nan" },
2104  { "assertIsInf", "assert_is_inf" },
2105  { "assertIsPlusInf", "assert_is_plus_inf" },
2106  { "assertIsMinusInf", "assert_is_minus_inf" },
2107  { "assertIsPlusZero", "assert_is_plus_zero" },
2108  { "assertIsMinusZero", "assert_is_minus_zero" },
2109  { "assertIsFunction", "assert_is_function" },
2110  { "assertIsThread", "assert_is_thread" },
2111  { "assertIsUserdata", "assert_is_userdata" },
2112
2113  -- type assertions: assertIsXXX -> assertXxx
2114  { "assertIsNumber", "assertNumber" },
2115  { "assertIsString", "assertString" },
2116  { "assertIsTable", "assertTable" },
2117  { "assertIsBoolean", "assertBoolean" },
2118  { "assertIsNil", "assertNil" },
2119  { "assertIsTrue", "assertTrue" },
2120  { "assertIsFalse", "assertFalse" },
2121  { "assertIsNaN", "assertNaN" },
2122  { "assertIsInf", "assertInf" },
2123  { "assertIsPlusInf", "assertPlusInf" },
2124  { "assertIsMinusInf", "assertMinusInf" },
2125  { "assertIsPlusZero", "assertPlusZero" },
2126  { "assertIsMinusZero", "assertMinusZero" },
2127  { "assertIsFunction", "assertFunction" },
2128  { "assertIsThread", "assertThread" },
2129  { "assertIsUserdata", "assertUserdata" }, -- type assertions: assertIsXXX -> assert_xxx (luaunit v2 compat)
2130  { "assertIsNumber", "assert_number" },
2131  { "assertIsString", "assert_string" },
2132  { "assertIsTable", "assert_table" },
2133  { "assertIsBoolean", "assert_boolean" },
2134  { "assertIsNil", "assert_nil" },
2135  { "assertIsTrue", "assert_true" },
2136  { "assertIsFalse", "assert_false" },
2137  { "assertIsNaN", "assert_nan" },
2138  { "assertIsInf", "assert_inf" },
2139  { "assertIsPlusInf", "assert_plus_inf" },
2140  { "assertIsMinusInf", "assert_minus_inf" },
2141  { "assertIsPlusZero", "assert_plus_zero" },
2142  { "assertIsMinusZero", "assert_minus_zero" },
2143  { "assertIsFunction", "assert_function" },
2144  { "assertIsThread", "assert_thread" },
2145  { "assertIsUserdata", "assert_userdata" }, -- type assertions: assertNotIsXXX -> assert_not_is_xxx
2146  { "assertNotIsNumber", "assert_not_is_number" },
2147  { "assertNotIsString", "assert_not_is_string" },
2148  { "assertNotIsTable", "assert_not_is_table" },
2149  { "assertNotIsBoolean", "assert_not_is_boolean" },
2150  { "assertNotIsNil", "assert_not_is_nil" },
2151  { "assertNotIsTrue", "assert_not_is_true" },
2152  { "assertNotIsFalse", "assert_not_is_false" },
2153  { "assertNotIsNaN", "assert_not_is_nan" },
2154  { "assertNotIsInf", "assert_not_is_inf" },
2155  { "assertNotIsPlusInf", "assert_not_plus_inf" },
2156  { "assertNotIsMinusInf", "assert_not_minus_inf" },
2157  { "assertNotIsPlusZero", "assert_not_plus_zero" },
2158  { "assertNotIsMinusZero", "assert_not_minus_zero" },
2159  { "assertNotIsFunction", "assert_not_is_function" },
2160  { "assertNotIsThread", "assert_not_is_thread" },
2161  { "assertNotIsUserdata", "assert_not_is_userdata" }, -- type assertions: assertNotIsXXX -> assertNotXxx (luaunit v2 compat)
2162  { "assertNotIsNumber", "assertNotNumber" },
2163  { "assertNotIsString", "assertNotString" },
2164  { "assertNotIsTable", "assertNotTable" },
2165  { "assertNotIsBoolean", "assertNotBoolean" },
2166  { "assertNotIsNil", "assertNotNil" },
2167  { "assertNotIsTrue", "assertNotTrue" },
2168  { "assertNotIsFalse", "assertNotFalse" },
2169  { "assertNotIsNaN", "assertNotNaN" },
2170  { "assertNotIsInf", "assertNotInf" },
2171  { "assertNotIsPlusInf", "assertNotPlusInf" },
2172  { "assertNotIsMinusInf", "assertNotMinusInf" },
2173  { "assertNotIsPlusZero", "assertNotPlusZero" },
2174  { "assertNotIsMinusZero", "assertNotMinusZero" },
2175  { "assertNotIsFunction", "assertNotFunction" },
2176  { "assertNotIsThread", "assertNotThread" },
2177  { "assertNotIsUserdata", "assertNotUserdata" }, -- type assertions: assertNotIsXXX -> assert_not_xxx
2178  { "assertNotIsNumber", "assert_not_number" },
2179  { "assertNotIsString", "assert_not_string" },
2180  { "assertNotIsTable", "assert_not_table" },
2181  { "assertNotIsBoolean", "assert_not_boolean" },
2182  { "assertNotIsNil", "assert_not_nil" },
2183  { "assertNotIsTrue", "assert_not_true" },
2184  { "assertNotIsFalse", "assert_not_false" },
2185  { "assertNotIsNaN", "assert_not_nan" },
2186  { "assertNotIsInf", "assert_not_inf" },
2187  { "assertNotIsPlusInf", "assert_not_plus_inf" },
2188  { "assertNotIsMinusInf", "assert_not_minus_inf" },
2189  { "assertNotIsPlusZero", "assert_not_plus_zero" },
2190  { "assertNotIsMinusZero", "assert_not_minus_zero" },
2191  { "assertNotIsFunction", "assert_not_function" },
2192  { "assertNotIsThread", "assert_not_thread" },
2193  { "assertNotIsUserdata", "assert_not_userdata" }, -- all assertions with Coroutine duplicate Thread assertions
2194  { "assertIsThread", "assertIsCoroutine" },
2195  { "assertIsThread", "assertCoroutine" },
2196  { "assertIsThread", "assert_is_coroutine" },
2197  { "assertIsThread", "assert_coroutine" },
2198  { "assertNotIsThread", "assertNotIsCoroutine" },
2199  { "assertNotIsThread", "assertNotCoroutine" },
2200  { "assertNotIsThread", "assert_not_is_coroutine" },
2201  { "assertNotIsThread", "assert_not_coroutine" },
2202}
2203
2204-- Create all aliases in M
2205for _, v in ipairs(list_of_funcs) do
2206  local funcname, alias = v[1], v[2]
2207  M[alias] = M[funcname]
2208
2209  if EXPORT_ASSERT_TO_GLOBALS then
2210    _G[funcname] = M[funcname]
2211    _G[alias] = M[funcname]
2212  end
2213end
2214
2215----------------------------------------------------------------
2216--
2217--                     Outputters
2218--
2219----------------------------------------------------------------
2220
2221-- A common "base" class for outputters
2222-- For concepts involved (class inheritance) see http://www.lua.org/pil/16.2.html
2223
2224local genericOutput = { __class__ = "genericOutput" } -- class
2225local genericOutput_MT = { __index = genericOutput } -- metatable
2226M.genericOutput = genericOutput -- publish, so that custom classes may derive from it
2227
2228function genericOutput.new(runner, default_verbosity)
2229  -- runner is the "parent" object controlling the output, usually a LuaUnit instance
2230  local t = { runner = runner }
2231  if runner then
2232    t.result = runner.result
2233    t.verbosity = runner.verbosity or default_verbosity
2234    t.fname = runner.fname
2235  else
2236    t.verbosity = default_verbosity
2237  end
2238  return setmetatable(t, genericOutput_MT)
2239end
2240
2241-- abstract ("empty") methods
2242function genericOutput:startSuite()
2243  -- Called once, when the suite is started
2244end
2245
2246function genericOutput:startClass(className)
2247  -- Called each time a new test class is started
2248end
2249
2250function genericOutput:startTest(testName)
2251  -- called each time a new test is started, right before the setUp()
2252  -- the current test status node is already created and available in: self.result.currentNode
2253end
2254
2255function genericOutput:updateStatus(node)
2256  -- called with status failed or error as soon as the error/failure is encountered
2257  -- this method is NOT called for a successful test because a test is marked as successful by default
2258  -- and does not need to be updated
2259end
2260
2261function genericOutput:endTest(node)
2262  -- called when the test is finished, after the tearDown() method
2263end
2264
2265function genericOutput:endClass()
2266  -- called when executing the class is finished, before moving on to the next class of at the end of the test execution
2267end
2268
2269function genericOutput:endSuite()
2270  -- called at the end of the test suite execution
2271end
2272
2273----------------------------------------------------------------
2274--                     class TapOutput
2275----------------------------------------------------------------
2276
2277local TapOutput = genericOutput.new() -- derived class
2278local TapOutput_MT = { __index = TapOutput } -- metatable
2279TapOutput.__class__ = "TapOutput"
2280
2281-- For a good reference for TAP format, check: http://testanything.org/tap-specification.html
2282
2283function TapOutput.new(runner)
2284  local t = genericOutput.new(runner, M.VERBOSITY_LOW)
2285  return setmetatable(t, TapOutput_MT)
2286end
2287function TapOutput:startSuite()
2288  print("1.." .. self.result.selectedCount)
2289  print("# Started on " .. self.result.startDate)
2290end
2291function TapOutput:startClass(className)
2292  if className ~= "[TestFunctions]" then
2293    print("# Starting class: " .. className)
2294  end
2295end
2296
2297function TapOutput:updateStatus(node)
2298  if node:isSkipped() then
2299    io.stdout:write("ok ", self.result.currentTestNumber, "\t# SKIP ", node.msg, "\n")
2300    return
2301  end
2302
2303  io.stdout:write("not ok ", self.result.currentTestNumber, "\t", node.testName, "\n")
2304  if self.verbosity > M.VERBOSITY_LOW then
2305    print(prefixString("#   ", node.msg))
2306  end
2307  if (node:isFailure() or node:isError()) and self.verbosity > M.VERBOSITY_DEFAULT then
2308    print(prefixString("#   ", node.stackTrace))
2309  end
2310end
2311
2312function TapOutput:endTest(node)
2313  if node:isSuccess() then
2314    io.stdout:write("ok     ", self.result.currentTestNumber, "\t", node.testName, "\n")
2315  end
2316end
2317
2318function TapOutput:endSuite()
2319  print("# " .. M.LuaUnit.statusLine(self.result))
2320  return self.result.notSuccessCount
2321end
2322
2323-- class TapOutput end
2324
2325----------------------------------------------------------------
2326--                     class JUnitOutput
2327----------------------------------------------------------------
2328
2329-- See directory junitxml for more information about the junit format
2330local JUnitOutput = genericOutput.new() -- derived class
2331local JUnitOutput_MT = { __index = JUnitOutput } -- metatable
2332JUnitOutput.__class__ = "JUnitOutput"
2333
2334function JUnitOutput.new(runner)
2335  local t = genericOutput.new(runner, M.VERBOSITY_LOW)
2336  t.testList = {}
2337  return setmetatable(t, JUnitOutput_MT)
2338end
2339
2340function JUnitOutput:startSuite()
2341  -- open xml file early to deal with errors
2342  if self.fname == nil then
2343    error("With Junit, an output filename must be supplied with --name!")
2344  end
2345  if string.sub(self.fname, -4) ~= ".xml" then
2346    self.fname = self.fname .. ".xml"
2347  end
2348  self.fd = io.open(self.fname, "w")
2349  if self.fd == nil then
2350    error("Could not open file for writing: " .. self.fname)
2351  end
2352
2353  print("# XML output to " .. self.fname)
2354  print("# Started on " .. self.result.startDate)
2355end
2356function JUnitOutput:startClass(className)
2357  if className ~= "[TestFunctions]" then
2358    print("# Starting class: " .. className)
2359  end
2360end
2361function JUnitOutput:startTest(testName)
2362  print("# Starting test: " .. testName)
2363end
2364
2365function JUnitOutput:updateStatus(node)
2366  if node:isFailure() then
2367    print("#   Failure: " .. prefixString("#   ", node.msg):sub(4, nil))
2368    -- print('# ' .. node.stackTrace)
2369  elseif node:isError() then
2370    print("#   Error: " .. prefixString("#   ", node.msg):sub(4, nil))
2371    -- print('# ' .. node.stackTrace)
2372  end
2373end
2374
2375function JUnitOutput:endSuite()
2376  print("# " .. M.LuaUnit.statusLine(self.result))
2377
2378  -- XML file writing
2379  self.fd:write('<?xml version="1.0" encoding="UTF-8" ?>\n')
2380  self.fd:write("<testsuites>\n")
2381  self.fd:write(
2382    string.format(
2383      '    <testsuite name="LuaUnit" id="00001" package="" hostname="localhost" tests="%d" timestamp="%s" time="%0.3f" errors="%d" failures="%d" skipped="%d">\n',
2384      self.result.runCount,
2385      self.result.startIsodate,
2386      self.result.duration,
2387      self.result.errorCount,
2388      self.result.failureCount,
2389      self.result.skippedCount
2390    )
2391  )
2392  self.fd:write("        <properties>\n")
2393  self.fd:write(string.format('            <property name="Lua Version" value="%s"/>\n', _VERSION))
2394  self.fd:write(string.format('            <property name="LuaUnit Version" value="%s"/>\n', M.VERSION))
2395  -- XXX please include system name and version if possible
2396  self.fd:write("        </properties>\n")
2397
2398  for i, node in ipairs(self.result.allTests) do
2399    self.fd:write(
2400      string.format(
2401        '        <testcase classname="%s" name="%s" time="%0.3f">\n',
2402        node.className,
2403        node.testName,
2404        node.duration
2405      )
2406    )
2407    if node:isNotSuccess() then
2408      self.fd:write(node:statusXML())
2409    end
2410    self.fd:write("        </testcase>\n")
2411  end
2412
2413  -- Next two lines are needed to validate junit ANT xsd, but really not useful in general:
2414  self.fd:write("    <system-out/>\n")
2415  self.fd:write("    <system-err/>\n")
2416
2417  self.fd:write("    </testsuite>\n")
2418  self.fd:write("</testsuites>\n")
2419  self.fd:close()
2420  return self.result.notSuccessCount
2421end
2422
2423-- class TapOutput end
2424
2425----------------------------------------------------------------
2426--                     class TextOutput
2427----------------------------------------------------------------
2428
2429--[[    Example of other unit-tests suite text output
2430
2431-- Python Non verbose:
2432
2433For each test: . or F or E
2434
2435If some failed tests:
2436    ==============
2437    ERROR / FAILURE: TestName (testfile.testclass)
2438    ---------
2439    Stack trace
2440
2441
2442then --------------
2443then "Ran x tests in 0.000s"
2444then OK or FAILED (failures=1, error=1)
2445
2446-- Python Verbose:
2447testname (filename.classname) ... ok
2448testname (filename.classname) ... FAIL
2449testname (filename.classname) ... ERROR
2450
2451then --------------
2452then "Ran x tests in 0.000s"
2453then OK or FAILED (failures=1, error=1)
2454
2455-- Ruby:
2456Started
2457 .
2458 Finished in 0.002695 seconds.
2459
2460 1 tests, 2 assertions, 0 failures, 0 errors
2461
2462-- Ruby:
2463>> ruby tc_simple_number2.rb
2464Loaded suite tc_simple_number2
2465Started
2466F..
2467Finished in 0.038617 seconds.
2468
2469  1) Failure:
2470test_failure(TestSimpleNumber) [tc_simple_number2.rb:16]:
2471Adding doesn't work.
2472<3> expected but was
2473<4>.
2474
24753 tests, 4 assertions, 1 failures, 0 errors
2476
2477-- Java Junit
2478.......F.
2479Time: 0,003
2480There was 1 failure:
24811) testCapacity(junit.samples.VectorTest)junit.framework.AssertionFailedError
2482    at junit.samples.VectorTest.testCapacity(VectorTest.java:87)
2483    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
2484    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
2485    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
2486
2487FAILURES!!!
2488Tests run: 8,  Failures: 1,  Errors: 0
2489
2490
2491-- Maven
2492
2493# mvn test
2494-------------------------------------------------------
2495 T E S T S
2496-------------------------------------------------------
2497Running math.AdditionTest
2498Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed:
24990.03 sec <<< FAILURE!
2500
2501Results :
2502
2503Failed tests:
2504  testLireSymbole(math.AdditionTest)
2505
2506Tests run: 2, Failures: 1, Errors: 0, Skipped: 0
2507
2508
2509-- LuaUnit
2510---- non verbose
2511* display . or F or E when running tests
2512---- verbose
2513* display test name + ok/fail
2514----
2515* blank line
2516* number) ERROR or FAILURE: TestName
2517   Stack trace
2518* blank line
2519* number) ERROR or FAILURE: TestName
2520   Stack trace
2521
2522then --------------
2523then "Ran x tests in 0.000s (%d not selected, %d skipped)"
2524then OK or FAILED (failures=1, error=1)
2525
2526
2527]]
2528
2529local TextOutput = genericOutput.new() -- derived class
2530local TextOutput_MT = { __index = TextOutput } -- metatable
2531TextOutput.__class__ = "TextOutput"
2532
2533function TextOutput.new(runner)
2534  local t = genericOutput.new(runner, M.VERBOSITY_DEFAULT)
2535  t.errorList = {}
2536  return setmetatable(t, TextOutput_MT)
2537end
2538
2539function TextOutput:startSuite()
2540  if self.verbosity > M.VERBOSITY_DEFAULT then
2541    print("Started on " .. self.result.startDate)
2542  end
2543end
2544
2545function TextOutput:startTest(testName)
2546  if self.verbosity > M.VERBOSITY_DEFAULT then
2547    io.stdout:write("    ", self.result.currentNode.testName, " ... ")
2548  end
2549end
2550
2551function TextOutput:endTest(node)
2552  if node:isSuccess() then
2553    if self.verbosity > M.VERBOSITY_DEFAULT then
2554      io.stdout:write("Ok\n")
2555    else
2556      io.stdout:write(".")
2557      io.stdout:flush()
2558    end
2559  else
2560    if self.verbosity > M.VERBOSITY_DEFAULT then
2561      print(node.status)
2562      print(node.msg)
2563      --[[
2564                -- find out when to do this:
2565                if self.verbosity > M.VERBOSITY_DEFAULT then
2566                    print( node.stackTrace )
2567                end
2568                ]]
2569    else
2570      -- write only the first character of status E, F or S
2571      io.stdout:write(string.sub(node.status, 1, 1))
2572      io.stdout:flush()
2573    end
2574  end
2575end
2576
2577function TextOutput:displayOneFailedTest(index, fail)
2578  print(index .. ") " .. fail.testName)
2579  print(fail.msg)
2580  print(fail.stackTrace)
2581  print()
2582end
2583
2584function TextOutput:displayErroredTests()
2585  if #self.result.errorTests ~= 0 then
2586    print("Tests with errors:")
2587    print("------------------")
2588    for i, v in ipairs(self.result.errorTests) do
2589      self:displayOneFailedTest(i, v)
2590    end
2591  end
2592end
2593
2594function TextOutput:displayFailedTests()
2595  if #self.result.failedTests ~= 0 then
2596    print("Failed tests:")
2597    print("-------------")
2598    for i, v in ipairs(self.result.failedTests) do
2599      self:displayOneFailedTest(i, v)
2600    end
2601  end
2602end
2603
2604function TextOutput:endSuite()
2605  if self.verbosity > M.VERBOSITY_DEFAULT then
2606    print("=========================================================")
2607  else
2608    print()
2609  end
2610  self:displayErroredTests()
2611  self:displayFailedTests()
2612  print(M.LuaUnit.statusLine(self.result))
2613  if self.result.notSuccessCount == 0 then
2614    print("OK")
2615  end
2616end
2617
2618-- class TextOutput end
2619
2620----------------------------------------------------------------
2621--                     class NilOutput
2622----------------------------------------------------------------
2623
2624local function nopCallable()
2625  -- print(42)
2626  return nopCallable
2627end
2628
2629local NilOutput = { __class__ = "NilOuptut" } -- class
2630local NilOutput_MT = { __index = nopCallable } -- metatable
2631
2632function NilOutput.new(runner)
2633  return setmetatable({ __class__ = "NilOutput" }, NilOutput_MT)
2634end
2635
2636----------------------------------------------------------------
2637--
2638--                     class LuaUnit
2639--
2640----------------------------------------------------------------
2641
2642M.LuaUnit = { outputType = TextOutput, verbosity = M.VERBOSITY_DEFAULT, __class__ = "LuaUnit", instances = {} }
2643local LuaUnit_MT = { __index = M.LuaUnit }
2644
2645if EXPORT_ASSERT_TO_GLOBALS then
2646  LuaUnit = M.LuaUnit
2647end
2648
2649function M.LuaUnit.new()
2650  local newInstance = setmetatable({}, LuaUnit_MT)
2651  return newInstance
2652end
2653
2654-----------------[[ Utility methods ]]---------------------
2655
2656function M.LuaUnit.asFunction(aObject)
2657  -- return "aObject" if it is a function, and nil otherwise
2658  if "function" == type(aObject) then
2659    return aObject
2660  end
2661end
2662
2663function M.LuaUnit.splitClassMethod(someName)
2664  --[[
2665        Return a pair of className, methodName strings for a name in the form
2666        "class.method". If no class part (or separator) is found, will return
2667        nil, someName instead (the latter being unchanged).
2668
2669        This convention thus also replaces the older isClassMethod() test:
2670        You just have to check for a non-nil className (return) value.
2671        ]]
2672  local separator = string.find(someName, ".", 1, true)
2673  if separator then
2674    return someName:sub(1, separator - 1), someName:sub(separator + 1)
2675  end
2676  return nil, someName
2677end
2678
2679function M.LuaUnit.isMethodTestName(s)
2680  -- return true is the name matches the name of a test method
2681  -- default rule is that is starts with 'Test' or with 'test'
2682  return string.sub(s, 1, 4):lower() == "test"
2683end
2684
2685function M.LuaUnit.isTestName(s)
2686  -- return true is the name matches the name of a test
2687  -- default rule is that is starts with 'Test' or with 'test'
2688  return string.sub(s, 1, 4):lower() == "test"
2689end
2690
2691function M.LuaUnit.collectTests()
2692  -- return a list of all test names in the global namespace
2693  -- that match LuaUnit.isTestName
2694
2695  local testNames = {}
2696  for k, _ in pairs(_G) do
2697    if type(k) == "string" and M.LuaUnit.isTestName(k) then
2698      table.insert(testNames, k)
2699    end
2700  end
2701  table.sort(testNames)
2702  return testNames
2703end
2704
2705function M.LuaUnit.parseCmdLine(cmdLine)
2706  -- parse the command line
2707  -- Supported command line parameters:
2708  -- --verbose, -v: increase verbosity
2709  -- --quiet, -q: silence output
2710  -- --error, -e: treat errors as fatal (quit program)
2711  -- --output, -o, + name: select output type
2712  -- --pattern, -p, + pattern: run test matching pattern, may be repeated
2713  -- --exclude, -x, + pattern: run test not matching pattern, may be repeated
2714  -- --shuffle, -s, : shuffle tests before reunning them
2715  -- --name, -n, + fname: name of output file for junit, default to stdout
2716  -- --repeat, -r, + num: number of times to execute each test
2717  -- [testnames, ...]: run selected test names
2718  --
2719  -- Returns a table with the following fields:
2720  -- verbosity: nil, M.VERBOSITY_DEFAULT, M.VERBOSITY_QUIET, M.VERBOSITY_VERBOSE
2721  -- output: nil, 'tap', 'junit', 'text', 'nil'
2722  -- testNames: nil or a list of test names to run
2723  -- exeRepeat: num or 1
2724  -- pattern: nil or a list of patterns
2725  -- exclude: nil or a list of patterns
2726
2727  local result, state = {}, nil
2728  local SET_OUTPUT = 1
2729  local SET_PATTERN = 2
2730  local SET_EXCLUDE = 3
2731  local SET_FNAME = 4
2732  local SET_REPEAT = 5
2733
2734  if cmdLine == nil then
2735    return result
2736  end
2737
2738  local function parseOption(option)
2739    if option == "--help" or option == "-h" then
2740      result["help"] = true
2741      return
2742    elseif option == "--version" then
2743      result["version"] = true
2744      return
2745    elseif option == "--verbose" or option == "-v" then
2746      result["verbosity"] = M.VERBOSITY_VERBOSE
2747      return
2748    elseif option == "--quiet" or option == "-q" then
2749      result["verbosity"] = M.VERBOSITY_QUIET
2750      return
2751    elseif option == "--error" or option == "-e" then
2752      result["quitOnError"] = true
2753      return
2754    elseif option == "--failure" or option == "-f" then
2755      result["quitOnFailure"] = true
2756      return
2757    elseif option == "--shuffle" or option == "-s" then
2758      result["shuffle"] = true
2759      return
2760    elseif option == "--output" or option == "-o" then
2761      state = SET_OUTPUT
2762      return state
2763    elseif option == "--name" or option == "-n" then
2764      state = SET_FNAME
2765      return state
2766    elseif option == "--repeat" or option == "-r" then
2767      state = SET_REPEAT
2768      return state
2769    elseif option == "--pattern" or option == "-p" then
2770      state = SET_PATTERN
2771      return state
2772    elseif option == "--exclude" or option == "-x" then
2773      state = SET_EXCLUDE
2774      return state
2775    end
2776    error("Unknown option: " .. option, 3)
2777  end
2778
2779  local function setArg(cmdArg, state)
2780    if state == SET_OUTPUT then
2781      result["output"] = cmdArg
2782      return
2783    elseif state == SET_FNAME then
2784      result["fname"] = cmdArg
2785      return
2786    elseif state == SET_REPEAT then
2787      result["exeRepeat"] = tonumber(cmdArg) or error("Malformed -r argument: " .. cmdArg)
2788      return
2789    elseif state == SET_PATTERN then
2790      if result["pattern"] then
2791        table.insert(result["pattern"], cmdArg)
2792      else
2793        result["pattern"] = { cmdArg }
2794      end
2795      return
2796    elseif state == SET_EXCLUDE then
2797      local notArg = "!" .. cmdArg
2798      if result["pattern"] then
2799        table.insert(result["pattern"], notArg)
2800      else
2801        result["pattern"] = { notArg }
2802      end
2803      return
2804    end
2805    error("Unknown parse state: " .. state)
2806  end
2807
2808  for i, cmdArg in ipairs(cmdLine) do
2809    if state ~= nil then
2810      setArg(cmdArg, state, result)
2811      state = nil
2812    else
2813      if cmdArg:sub(1, 1) == "-" then
2814        state = parseOption(cmdArg)
2815      else
2816        if result["testNames"] then
2817          table.insert(result["testNames"], cmdArg)
2818        else
2819          result["testNames"] = { cmdArg }
2820        end
2821      end
2822    end
2823  end
2824
2825  if result["help"] then
2826    M.LuaUnit.help()
2827  end
2828
2829  if result["version"] then
2830    M.LuaUnit.version()
2831  end
2832
2833  if state ~= nil then
2834    error("Missing argument after " .. cmdLine[#cmdLine], 2)
2835  end
2836
2837  return result
2838end
2839
2840function M.LuaUnit.help()
2841  print(M.USAGE)
2842  os.exit(0)
2843end
2844
2845function M.LuaUnit.version()
2846  print("LuaUnit v" .. M.VERSION .. " by Philippe Fremy <phil@freehackers.org>")
2847  os.exit(0)
2848end
2849
2850----------------------------------------------------------------
2851--                     class NodeStatus
2852----------------------------------------------------------------
2853
2854local NodeStatus = { __class__ = "NodeStatus" } -- class
2855local NodeStatus_MT = { __index = NodeStatus } -- metatable
2856M.NodeStatus = NodeStatus
2857
2858-- values of status
2859NodeStatus.SUCCESS = "SUCCESS"
2860NodeStatus.SKIP = "SKIP"
2861NodeStatus.FAIL = "FAIL"
2862NodeStatus.ERROR = "ERROR"
2863
2864function NodeStatus.new(number, testName, className)
2865  -- default constructor, test are PASS by default
2866  local t = { number = number, testName = testName, className = className }
2867  setmetatable(t, NodeStatus_MT)
2868  t:success()
2869  return t
2870end
2871
2872function NodeStatus:success()
2873  self.status = self.SUCCESS
2874  -- useless because lua does this for us, but it helps me remembering the relevant field names
2875  self.msg = nil
2876  self.stackTrace = nil
2877end
2878
2879function NodeStatus:skip(msg)
2880  self.status = self.SKIP
2881  self.msg = msg
2882  self.stackTrace = nil
2883end
2884
2885function NodeStatus:fail(msg, stackTrace)
2886  self.status = self.FAIL
2887  self.msg = msg
2888  self.stackTrace = stackTrace
2889end
2890
2891function NodeStatus:error(msg, stackTrace)
2892  self.status = self.ERROR
2893  self.msg = msg
2894  self.stackTrace = stackTrace
2895end
2896
2897function NodeStatus:isSuccess()
2898  return self.status == NodeStatus.SUCCESS
2899end
2900
2901function NodeStatus:isNotSuccess()
2902  -- Return true if node is either failure or error or skip
2903  return (self.status == NodeStatus.FAIL or self.status == NodeStatus.ERROR or self.status == NodeStatus.SKIP)
2904end
2905
2906function NodeStatus:isSkipped()
2907  return self.status == NodeStatus.SKIP
2908end
2909
2910function NodeStatus:isFailure()
2911  return self.status == NodeStatus.FAIL
2912end
2913
2914function NodeStatus:isError()
2915  return self.status == NodeStatus.ERROR
2916end
2917
2918function NodeStatus:statusXML()
2919  if self:isError() then
2920    return table.concat({
2921      '            <error type="',
2922      xmlEscape(self.msg),
2923      '">\n',
2924      "                <![CDATA[",
2925      xmlCDataEscape(self.stackTrace),
2926      "]]></error>\n",
2927    })
2928  elseif self:isFailure() then
2929    return table.concat({
2930      '            <failure type="',
2931      xmlEscape(self.msg),
2932      '">\n',
2933      "                <![CDATA[",
2934      xmlCDataEscape(self.stackTrace),
2935      "]]></failure>\n",
2936    })
2937  elseif self:isSkipped() then
2938    return table.concat({ "            <skipped>", xmlEscape(self.msg), "</skipped>\n" })
2939  end
2940  return "            <passed/>\n" -- (not XSD-compliant! normally shouldn't get here)
2941end
2942
2943--------------[[ Output methods ]]-------------------------
2944
2945local function conditional_plural(number, singular)
2946  -- returns a grammatically well-formed string "%d <singular/plural>"
2947  local suffix = ""
2948  if number ~= 1 then -- use plural
2949    suffix = (singular:sub(-2) == "ss") and "es" or "s"
2950  end
2951  return string.format("%d %s%s", number, singular, suffix)
2952end
2953
2954function M.LuaUnit.statusLine(result)
2955  -- return status line string according to results
2956  local s = {
2957    string.format("Ran %d tests in %0.3f seconds", result.runCount, result.duration),
2958    conditional_plural(result.successCount, "success"),
2959  }
2960  if result.notSuccessCount > 0 then
2961    if result.failureCount > 0 then
2962      table.insert(s, conditional_plural(result.failureCount, "failure"))
2963    end
2964    if result.errorCount > 0 then
2965      table.insert(s, conditional_plural(result.errorCount, "error"))
2966    end
2967  else
2968    table.insert(s, "0 failures")
2969  end
2970  if result.skippedCount > 0 then
2971    table.insert(s, string.format("%d skipped", result.skippedCount))
2972  end
2973  if result.nonSelectedCount > 0 then
2974    table.insert(s, string.format("%d non-selected", result.nonSelectedCount))
2975  end
2976  return table.concat(s, ", ")
2977end
2978
2979function M.LuaUnit:startSuite(selectedCount, nonSelectedCount)
2980  self.result = {
2981    selectedCount = selectedCount,
2982    nonSelectedCount = nonSelectedCount,
2983    successCount = 0,
2984    runCount = 0,
2985    currentTestNumber = 0,
2986    currentClassName = "",
2987    currentNode = nil,
2988    suiteStarted = true,
2989    startTime = os.clock(),
2990    startDate = os.date(os.getenv("LUAUNIT_DATEFMT")),
2991    startIsodate = os.date("%Y-%m-%dT%H:%M:%S"),
2992    patternIncludeFilter = self.patternIncludeFilter,
2993
2994    -- list of test node status
2995    allTests = {},
2996    failedTests = {},
2997    errorTests = {},
2998    skippedTests = {},
2999
3000    failureCount = 0,
3001    errorCount = 0,
3002    notSuccessCount = 0,
3003    skippedCount = 0,
3004  }
3005
3006  self.outputType = self.outputType or TextOutput
3007  self.output = self.outputType.new(self)
3008  self.output:startSuite()
3009end
3010
3011function M.LuaUnit:startClass(className, classInstance)
3012  self.result.currentClassName = className
3013  self.output:startClass(className)
3014  self:setupClass(className, classInstance)
3015end
3016
3017function M.LuaUnit:startTest(testName)
3018  self.result.currentTestNumber = self.result.currentTestNumber + 1
3019  self.result.runCount = self.result.runCount + 1
3020  self.result.currentNode = NodeStatus.new(self.result.currentTestNumber, testName, self.result.currentClassName)
3021  self.result.currentNode.startTime = os.clock()
3022  table.insert(self.result.allTests, self.result.currentNode)
3023  self.output:startTest(testName)
3024end
3025
3026function M.LuaUnit:updateStatus(err)
3027  -- "err" is expected to be a table / result from protectedCall()
3028  if err.status == NodeStatus.SUCCESS then
3029    return
3030  end
3031
3032  local node = self.result.currentNode
3033
3034  --[[ As a first approach, we will report only one error or one failure for one test.
3035
3036        However, we can have the case where the test is in failure, and the teardown is in error.
3037        In such case, it's a good idea to report both a failure and an error in the test suite. This is
3038        what Python unittest does for example. However, it mixes up counts so need to be handled carefully: for
3039        example, there could be more (failures + errors) count that tests. What happens to the current node ?
3040
3041        We will do this more intelligent version later.
3042        ]]
3043
3044  -- if the node is already in failure/error, just don't report the new error (see above)
3045  if node.status ~= NodeStatus.SUCCESS then
3046    return
3047  end
3048
3049  if err.status == NodeStatus.FAIL then
3050    node:fail(err.msg, err.trace)
3051    table.insert(self.result.failedTests, node)
3052  elseif err.status == NodeStatus.ERROR then
3053    node:error(err.msg, err.trace)
3054    table.insert(self.result.errorTests, node)
3055  elseif err.status == NodeStatus.SKIP then
3056    node:skip(err.msg)
3057    table.insert(self.result.skippedTests, node)
3058  else
3059    error("No such status: " .. prettystr(err.status))
3060  end
3061
3062  self.output:updateStatus(node)
3063end
3064
3065function M.LuaUnit:endTest()
3066  local node = self.result.currentNode
3067  -- print( 'endTest() '..prettystr(node))
3068  -- print( 'endTest() '..prettystr(node:isNotSuccess()))
3069  node.duration = os.clock() - node.startTime
3070  node.startTime = nil
3071  self.output:endTest(node)
3072
3073  if node:isSuccess() then
3074    self.result.successCount = self.result.successCount + 1
3075  elseif node:isError() then
3076    if self.quitOnError or self.quitOnFailure then
3077      -- Runtime error - abort test execution as requested by
3078      -- "--error" option. This is done by setting a special
3079      -- flag that gets handled in internalRunSuiteByInstances().
3080      print("\nERROR during LuaUnit test execution:\n" .. node.msg)
3081      self.result.aborted = true
3082    end
3083  elseif node:isFailure() then
3084    if self.quitOnFailure then
3085      -- Failure - abort test execution as requested by
3086      -- "--failure" option. This is done by setting a special
3087      -- flag that gets handled in internalRunSuiteByInstances().
3088      print("\nFailure during LuaUnit test execution:\n" .. node.msg)
3089      self.result.aborted = true
3090    end
3091  elseif node:isSkipped() then
3092    self.result.runCount = self.result.runCount - 1
3093  else
3094    error("No such node status: " .. prettystr(node.status))
3095  end
3096  self.result.currentNode = nil
3097end
3098
3099function M.LuaUnit:endClass()
3100  self:teardownClass(self.lastClassName, self.lastClassInstance)
3101  self.output:endClass()
3102end
3103
3104function M.LuaUnit:endSuite()
3105  if self.result.suiteStarted == false then
3106    error("LuaUnit:endSuite() -- suite was already ended")
3107  end
3108  self.result.duration = os.clock() - self.result.startTime
3109  self.result.suiteStarted = false
3110
3111  -- Expose test counts for outputter's endSuite(). This could be managed
3112  -- internally instead by using the length of the lists of failed tests
3113  -- but unit tests rely on these fields being present.
3114  self.result.failureCount = #self.result.failedTests
3115  self.result.errorCount = #self.result.errorTests
3116  self.result.notSuccessCount = self.result.failureCount + self.result.errorCount
3117  self.result.skippedCount = #self.result.skippedTests
3118
3119  self.output:endSuite()
3120end
3121
3122function M.LuaUnit:setOutputType(outputType, fname)
3123  -- Configures LuaUnit runner output
3124  -- outputType is one of: NIL, TAP, JUNIT, TEXT
3125  -- when outputType is junit, the additional argument fname is used to set the name of junit output file
3126  -- for other formats, fname is ignored
3127  if outputType:upper() == "NIL" then
3128    self.outputType = NilOutput
3129    return
3130  end
3131  if outputType:upper() == "TAP" then
3132    self.outputType = TapOutput
3133    return
3134  end
3135  if outputType:upper() == "JUNIT" then
3136    self.outputType = JUnitOutput
3137    if fname then
3138      self.fname = fname
3139    end
3140    return
3141  end
3142  if outputType:upper() == "TEXT" then
3143    self.outputType = TextOutput
3144    return
3145  end
3146  error("No such format: " .. outputType, 2)
3147end
3148
3149--------------[[ Runner ]]-----------------
3150
3151function M.LuaUnit:protectedCall(classInstance, methodInstance, prettyFuncName)
3152  -- if classInstance is nil, this is just a function call
3153  -- else, it's method of a class being called.
3154
3155  local function err_handler(e)
3156    -- transform error into a table, adding the traceback information
3157    return { status = NodeStatus.ERROR, msg = e, trace = string.sub(debug.traceback("", 1), 2) }
3158  end
3159
3160  local ok, err
3161  if classInstance then
3162    -- stupid Lua < 5.2 does not allow xpcall with arguments so let's use a workaround
3163    ok, err = xpcall(function()
3164      methodInstance(classInstance)
3165    end, err_handler)
3166  else
3167    ok, err = xpcall(function()
3168      methodInstance()
3169    end, err_handler)
3170  end
3171  if ok then
3172    return { status = NodeStatus.SUCCESS }
3173  end
3174  -- print('ok="'..prettystr(ok)..'" err="'..prettystr(err)..'"')
3175
3176  local iter_msg
3177  iter_msg = self.exeRepeat and "iteration " .. self.currentCount
3178
3179  err.msg, err.status = M.adjust_err_msg_with_iter(err.msg, iter_msg)
3180
3181  if err.status == NodeStatus.SUCCESS or err.status == NodeStatus.SKIP then
3182    err.trace = nil
3183    return err
3184  end
3185
3186  -- reformat / improve the stack trace
3187  if prettyFuncName then -- we do have the real method name
3188    err.trace = err.trace:gsub("in (%a+) 'methodInstance'", "in %1 '" .. prettyFuncName .. "'")
3189  end
3190  if STRIP_LUAUNIT_FROM_STACKTRACE then
3191    err.trace = stripLuaunitTrace2(err.trace, err.msg)
3192  end
3193
3194  return err -- return the error "object" (table)
3195end
3196
3197function M.LuaUnit:execOneFunction(className, methodName, classInstance, methodInstance)
3198  -- When executing a test function, className and classInstance must be nil
3199  -- When executing a class method, all parameters must be set
3200
3201  if type(methodInstance) ~= "function" then
3202    self:unregisterSuite()
3203    error(tostring(methodName) .. " must be a function, not " .. type(methodInstance))
3204  end
3205
3206  local prettyFuncName
3207  if className == nil then
3208    className = "[TestFunctions]"
3209    prettyFuncName = methodName
3210  else
3211    prettyFuncName = className .. "." .. methodName
3212  end
3213
3214  if self.lastClassName ~= className then
3215    if self.lastClassName ~= nil then
3216      self:endClass()
3217    end
3218    self:startClass(className, classInstance)
3219    self.lastClassName = className
3220    self.lastClassInstance = classInstance
3221  end
3222
3223  self:startTest(prettyFuncName)
3224
3225  local node = self.result.currentNode
3226  for iter_n = 1, self.exeRepeat or 1 do
3227    if node:isNotSuccess() then
3228      break
3229    end
3230    self.currentCount = iter_n
3231
3232    -- run setUp first (if any)
3233    if classInstance then
3234      local func = self.asFunction(classInstance.setUp)
3235        or self.asFunction(classInstance.Setup)
3236        or self.asFunction(classInstance.setup)
3237        or self.asFunction(classInstance.SetUp)
3238      if func then
3239        self:updateStatus(self:protectedCall(classInstance, func, className .. ".setUp"))
3240      end
3241    end
3242
3243    -- run testMethod()
3244    if node:isSuccess() then
3245      self:updateStatus(self:protectedCall(classInstance, methodInstance, prettyFuncName))
3246    end
3247
3248    -- lastly, run tearDown (if any)
3249    if classInstance then
3250      local func = self.asFunction(classInstance.tearDown)
3251        or self.asFunction(classInstance.TearDown)
3252        or self.asFunction(classInstance.teardown)
3253        or self.asFunction(classInstance.Teardown)
3254      if func then
3255        self:updateStatus(self:protectedCall(classInstance, func, className .. ".tearDown"))
3256      end
3257    end
3258  end
3259
3260  self:endTest()
3261end
3262
3263function M.LuaUnit.expandOneClass(result, className, classInstance)
3264  --[[
3265        Input: a list of { name, instance }, a class name, a class instance
3266        Ouptut: modify result to add all test method instance in the form:
3267        { className.methodName, classInstance }
3268        ]]
3269  for methodName, methodInstance in sortedPairs(classInstance) do
3270    if M.LuaUnit.asFunction(methodInstance) and M.LuaUnit.isMethodTestName(methodName) then
3271      table.insert(result, { className .. "." .. methodName, classInstance })
3272    end
3273  end
3274end
3275
3276function M.LuaUnit.expandClasses(listOfNameAndInst)
3277  --[[
3278        -- expand all classes (provided as {className, classInstance}) to a list of {className.methodName, classInstance}
3279        -- functions and methods remain untouched
3280
3281        Input: a list of { name, instance }
3282
3283        Output:
3284        * { function name, function instance } : do nothing
3285        * { class.method name, class instance }: do nothing
3286        * { class name, class instance } : add all method names in the form of (className.methodName, classInstance)
3287        ]]
3288  local result = {}
3289
3290  for i, v in ipairs(listOfNameAndInst) do
3291    local name, instance = v[1], v[2]
3292    if M.LuaUnit.asFunction(instance) then
3293      table.insert(result, { name, instance })
3294    else
3295      if type(instance) ~= "table" then
3296        error(
3297          "Instance must be a table or a function, not a " .. type(instance) .. " with value " .. prettystr(instance)
3298        )
3299      end
3300      local className, methodName = M.LuaUnit.splitClassMethod(name)
3301      if className then
3302        local methodInstance = instance[methodName]
3303        if methodInstance == nil then
3304          error("Could not find method in class " .. tostring(className) .. " for method " .. tostring(methodName))
3305        end
3306        table.insert(result, { name, instance })
3307      else
3308        M.LuaUnit.expandOneClass(result, name, instance)
3309      end
3310    end
3311  end
3312
3313  return result
3314end
3315
3316function M.LuaUnit.applyPatternFilter(patternIncFilter, listOfNameAndInst)
3317  local included, excluded = {}, {}
3318  for i, v in ipairs(listOfNameAndInst) do
3319    -- local name, instance = v[1], v[2]
3320    if patternFilter(patternIncFilter, v[1]) then
3321      table.insert(included, v)
3322    else
3323      table.insert(excluded, v)
3324    end
3325  end
3326  return included, excluded
3327end
3328
3329local function getKeyInListWithGlobalFallback(key, listOfNameAndInst)
3330  local result = nil
3331  for i, v in ipairs(listOfNameAndInst) do
3332    if listOfNameAndInst[i][1] == key then
3333      result = listOfNameAndInst[i][2]
3334      break
3335    end
3336  end
3337  if not M.LuaUnit.asFunction(result) then
3338    result = _G[key]
3339  end
3340  return result
3341end
3342
3343function M.LuaUnit:setupSuite(listOfNameAndInst)
3344  local setupSuite = getKeyInListWithGlobalFallback("setupSuite", listOfNameAndInst)
3345  if self.asFunction(setupSuite) then
3346    self:updateStatus(self:protectedCall(nil, setupSuite, "setupSuite"))
3347  end
3348end
3349
3350function M.LuaUnit:teardownSuite(listOfNameAndInst)
3351  local teardownSuite = getKeyInListWithGlobalFallback("teardownSuite", listOfNameAndInst)
3352  if self.asFunction(teardownSuite) then
3353    self:updateStatus(self:protectedCall(nil, teardownSuite, "teardownSuite"))
3354  end
3355end
3356
3357function M.LuaUnit:setupClass(className, instance)
3358  if type(instance) == "table" and self.asFunction(instance.setupClass) then
3359    self:updateStatus(self:protectedCall(instance, instance.setupClass, className .. ".setupClass"))
3360  end
3361end
3362
3363function M.LuaUnit:teardownClass(className, instance)
3364  if type(instance) == "table" and self.asFunction(instance.teardownClass) then
3365    self:updateStatus(self:protectedCall(instance, instance.teardownClass, className .. ".teardownClass"))
3366  end
3367end
3368
3369function M.LuaUnit:internalRunSuiteByInstances(listOfNameAndInst)
3370  --[[ Run an explicit list of tests. Each item of the list must be one of:
3371        * { function name, function instance }
3372        * { class name, class instance }
3373        * { class.method name, class instance }
3374
3375        This function is internal to LuaUnit. The official API to perform this action is runSuiteByInstances()
3376        ]]
3377
3378  local expandedList = self.expandClasses(listOfNameAndInst)
3379  if self.shuffle then
3380    randomizeTable(expandedList)
3381  end
3382  local filteredList, filteredOutList = self.applyPatternFilter(self.patternIncludeFilter, expandedList)
3383
3384  self:startSuite(#filteredList, #filteredOutList)
3385  self:setupSuite(listOfNameAndInst)
3386
3387  for i, v in ipairs(filteredList) do
3388    local name, instance = v[1], v[2]
3389    if M.LuaUnit.asFunction(instance) then
3390      self:execOneFunction(nil, name, nil, instance)
3391    else
3392      -- expandClasses() should have already taken care of sanitizing the input
3393      assert(type(instance) == "table")
3394      local className, methodName = M.LuaUnit.splitClassMethod(name)
3395      assert(className ~= nil)
3396      local methodInstance = instance[methodName]
3397      assert(methodInstance ~= nil)
3398      self:execOneFunction(className, methodName, instance, methodInstance)
3399    end
3400    if self.result.aborted then
3401      break -- "--error" or "--failure" option triggered
3402    end
3403  end
3404
3405  if self.lastClassName ~= nil then
3406    self:endClass()
3407  end
3408
3409  self:teardownSuite(listOfNameAndInst)
3410  self:endSuite()
3411
3412  if self.result.aborted then
3413    print("LuaUnit ABORTED (as requested by --error or --failure option)")
3414    self:unregisterSuite()
3415    os.exit(-2)
3416  end
3417end
3418
3419function M.LuaUnit:internalRunSuiteByNames(listOfName)
3420  --[[ Run LuaUnit with a list of generic names, coming either from command-line or from global
3421            namespace analysis. Convert the list into a list of (name, valid instances (table or function))
3422            and calls internalRunSuiteByInstances.
3423        ]]
3424
3425  local instanceName, instance
3426  local listOfNameAndInst = {}
3427
3428  for i, name in ipairs(listOfName) do
3429    local className, methodName = M.LuaUnit.splitClassMethod(name)
3430    if className then
3431      instanceName = className
3432      instance = _G[instanceName]
3433
3434      if instance == nil then
3435        self:unregisterSuite()
3436        error("No such name in global space: " .. instanceName)
3437      end
3438
3439      if type(instance) ~= "table" then
3440        self:unregisterSuite()
3441        error("Instance of " .. instanceName .. " must be a table, not " .. type(instance))
3442      end
3443
3444      local methodInstance = instance[methodName]
3445      if methodInstance == nil then
3446        self:unregisterSuite()
3447        error("Could not find method in class " .. tostring(className) .. " for method " .. tostring(methodName))
3448      end
3449    else
3450      -- for functions and classes
3451      instanceName = name
3452      instance = _G[instanceName]
3453    end
3454
3455    if instance == nil then
3456      self:unregisterSuite()
3457      error("No such name in global space: " .. instanceName)
3458    end
3459
3460    if type(instance) ~= "table" and type(instance) ~= "function" then
3461      self:unregisterSuite()
3462      error("Name must match a function or a table: " .. instanceName)
3463    end
3464
3465    table.insert(listOfNameAndInst, { name, instance })
3466  end
3467
3468  self:internalRunSuiteByInstances(listOfNameAndInst)
3469end
3470
3471function M.LuaUnit.run(...)
3472  -- Run some specific test classes.
3473  -- If no arguments are passed, run the class names specified on the
3474  -- command line. If no class name is specified on the command line
3475  -- run all classes whose name starts with 'Test'
3476  --
3477  -- If arguments are passed, they must be strings of the class names
3478  -- that you want to run or generic command line arguments (-o, -p, -v, ...)
3479  local runner = M.LuaUnit.new()
3480  return runner:runSuite(...)
3481end
3482
3483function M.LuaUnit:registerSuite()
3484  -- register the current instance into our global array of instances
3485  -- print('-> Register suite')
3486  M.LuaUnit.instances[#M.LuaUnit.instances + 1] = self
3487end
3488
3489function M.unregisterCurrentSuite()
3490  -- force unregister the last registered suite
3491  table.remove(M.LuaUnit.instances, #M.LuaUnit.instances)
3492end
3493
3494function M.LuaUnit:unregisterSuite()
3495  -- print('<- Unregister suite')
3496  -- remove our current instqances from the global array of instances
3497  local instanceIdx = nil
3498  for i, instance in ipairs(M.LuaUnit.instances) do
3499    if instance == self then
3500      instanceIdx = i
3501      break
3502    end
3503  end
3504
3505  if instanceIdx ~= nil then
3506    table.remove(M.LuaUnit.instances, instanceIdx)
3507    -- print('Unregister done')
3508  end
3509end
3510
3511function M.LuaUnit:initFromArguments(...)
3512  --[[Parses all arguments from either command-line or direct call and set internal
3513        flags of LuaUnit runner according to it.
3514
3515        Return the list of names which were possibly passed on the command-line or as arguments
3516        ]]
3517  local args = { ... }
3518  if type(args[1]) == "table" and args[1].__class__ == "LuaUnit" then
3519    -- run was called with the syntax M.LuaUnit:runSuite()
3520    -- we support both M.LuaUnit.run() and M.LuaUnit:run()
3521    -- strip out the first argument self to make it a command-line argument list
3522    table.remove(args, 1)
3523  end
3524
3525  if #args == 0 then
3526    args = cmdline_argv
3527  end
3528
3529  local options = pcall_or_abort(M.LuaUnit.parseCmdLine, args)
3530
3531  -- We expect these option fields to be either `nil` or contain
3532  -- valid values, so it's safe to always copy them directly.
3533  self.verbosity = options.verbosity
3534  self.quitOnError = options.quitOnError
3535  self.quitOnFailure = options.quitOnFailure
3536
3537  self.exeRepeat = options.exeRepeat
3538  self.patternIncludeFilter = options.pattern
3539  self.shuffle = options.shuffle
3540
3541  options.output = options.output or os.getenv("LUAUNIT_OUTPUT")
3542  options.fname = options.fname or os.getenv("LUAUNIT_JUNIT_FNAME")
3543
3544  if options.output then
3545    if options.output:lower() == "junit" and options.fname == nil then
3546      print("With junit output, a filename must be supplied with -n or --name")
3547      os.exit(-1)
3548    end
3549    pcall_or_abort(self.setOutputType, self, options.output, options.fname)
3550  end
3551
3552  return options.testNames
3553end
3554
3555function M.LuaUnit:runSuite(...)
3556  testNames = self:initFromArguments(...)
3557  self:registerSuite()
3558  self:internalRunSuiteByNames(testNames or M.LuaUnit.collectTests())
3559  self:unregisterSuite()
3560  return self.result.notSuccessCount
3561end
3562
3563function M.LuaUnit:runSuiteByInstances(listOfNameAndInst, commandLineArguments)
3564  --[[
3565        Run all test functions or tables provided as input.
3566
3567        Input: a list of { name, instance }
3568            instance can either be a function or a table containing test functions starting with the prefix "test"
3569
3570        return the number of failures and errors, 0 meaning success
3571        ]]
3572  -- parse the command-line arguments
3573  testNames = self:initFromArguments(commandLineArguments)
3574  self:registerSuite()
3575  self:internalRunSuiteByInstances(listOfNameAndInst)
3576  self:unregisterSuite()
3577  return self.result.notSuccessCount
3578end
3579
3580-- class LuaUnit
3581
3582-- For compatbility with LuaUnit v2
3583M.run = M.LuaUnit.run
3584M.Run = function()
3585  os.exit(M.LuaUnit.run())
3586end
3587
3588function M:setVerbosity(verbosity)
3589  -- set the verbosity value (as integer)
3590  M.LuaUnit.verbosity = verbosity
3591end
3592M.set_verbosity = M.setVerbosity
3593M.SetVerbosity = M.setVerbosity
3594
3595return M