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