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