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