go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/quota/internal/luatest/testdata/luaunit/luaunit.lua (about)

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