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