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