You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

telescope.lua 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. --[[
  2. The MIT License
  3. Copyright (c) 2009-2012 [Norman Clarke](mailto:norman@njclarke.com)
  4. Permission is hereby granted, free of charge, to any person obtaining a copy of
  5. this software and associated documentation files (the "Software"), to deal in
  6. the Software without restriction, including without limitation the rights to
  7. use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  8. of the Software, and to permit persons to whom the Software is furnished to do
  9. so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in all
  11. copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  18. SOFTWARE.
  19. ]]--
  20. --- Telescope is a test library for Lua that allows for flexible, declarative
  21. -- tests. The documentation produced here is intended largely for developers
  22. -- working on Telescope. For information on using Telescope, please visit the
  23. -- project homepage at: <a href="http://github.com/norman/telescope">http://github.com/norman/telescope#readme</a>.
  24. -- @release 0.6
  25. -- @class module
  26. -- @module 'telescope'
  27. local _M = {}
  28. local compat_env = require 'compat_env'
  29. local getfenv = _G.getfenv or compat_env.getfenv
  30. local setfenv = _G.setfenv or compat_env.setfenv
  31. local _VERSION = "0.6.0"
  32. --- The status codes that can be returned by an invoked test. These should not be overridden.
  33. -- @name status_codes
  34. -- @class table
  35. -- @field err - This is returned when an invoked test results in an error
  36. -- rather than a passed or failed assertion.
  37. -- @field fail - This is returned when an invoked test contains one or more failing assertions.
  38. -- @field pass - This is returned when all of a test's assertions pass.
  39. -- @field pending - This is returned when a test does not have a corresponding function.
  40. -- @field unassertive - This is returned when an invoked test does not produce
  41. -- errors, but does not contain any assertions.
  42. local status_codes = {
  43. err = 2,
  44. fail = 4,
  45. pass = 8,
  46. pending = 16,
  47. unassertive = 32
  48. }
  49. --- Labels used to show the various <tt>status_codes</tt> as a single character.
  50. -- These can be overridden if you wish.
  51. -- @name status_labels
  52. -- @class table
  53. -- @see status_codes
  54. -- @field status_codes.err 'E'
  55. -- @field status_codes.fail 'F'
  56. -- @field status_codes.pass 'P'
  57. -- @field status_codes.pending '?'
  58. -- @field status_codes.unassertive 'U'
  59. local status_labels = {
  60. [status_codes.err] = 'E',
  61. [status_codes.fail] = 'F',
  62. [status_codes.pass] = 'P',
  63. [status_codes.pending] = '?',
  64. [status_codes.unassertive] = 'U'
  65. }
  66. --- The default names for context blocks. It defaults to "context", "spec" and
  67. -- "describe."
  68. -- @name context_aliases
  69. -- @class table
  70. local context_aliases = {"context", "describe", "spec"}
  71. --- The default names for test blocks. It defaults to "test," "it", "expect",
  72. -- "they" and "should."
  73. -- @name test_aliases
  74. -- @class table
  75. local test_aliases = {"test", "it", "expect", "should", "they"}
  76. --- The default names for "before" blocks. It defaults to "before" and "setup."
  77. -- The function in the before block will be run before each sibling test function
  78. -- or context.
  79. -- @name before_aliases
  80. -- @class table
  81. local before_aliases = {"before", "setup"}
  82. --- The default names for "after" blocks. It defaults to "after" and "teardown."
  83. -- The function in the after block will be run after each sibling test function
  84. -- or context.
  85. -- @name after_aliases
  86. -- @class table
  87. local after_aliases = {"after", "teardown"}
  88. -- Prefix to place before all assertion messages. Used by make_assertion().
  89. local assertion_message_prefix = "Assert failed: expected "
  90. --- The default assertions.
  91. -- These are the assertions built into telescope. You can override them or
  92. -- create your own custom assertions using <tt>make_assertion</tt>.
  93. -- <ul>
  94. -- <tt><li>assert_blank(a)</tt> - true if a is nil, or the empty string</li>
  95. -- <tt><li>assert_empty(a)</tt> - true if a is an empty table</li>
  96. -- <tt><li>assert_equal(a, b)</tt> - true if a == b</li>
  97. -- <tt><li>assert_error(f)</tt> - true if function f produces an error</li>
  98. -- <tt><li>assert_false(a)</tt> - true if a is false</li>
  99. -- <tt><li>assert_greater_than(a, b)</tt> - true if a > b</li>
  100. -- <tt><li>assert_gte(a, b)</tt> - true if a >= b</li>
  101. -- <tt><li>assert_less_than(a, b)</tt> - true if a < b</li>
  102. -- <tt><li>assert_lte(a, b)</tt> - true if a <= b</li>
  103. -- <tt><li>assert_match(a, b)</tt> - true if b is a string that matches pattern a</li>
  104. -- <tt><li>assert_nil(a)</tt> - true if a is nil</li>
  105. -- <tt><li>assert_true(a)</tt> - true if a is true</li>
  106. -- <tt><li>assert_type(a, b)</tt> - true if a is of type b</li>
  107. -- <tt><li>assert_not_blank(a)</tt> - true if a is not nil and a is not the empty string</li>
  108. -- <tt><li>assert_not_empty(a)</tt> - true if a is a table, and a is not empty</li>
  109. -- <tt><li>assert_not_equal(a, b)</tt> - true if a ~= b</li>
  110. -- <tt><li>assert_not_error(f)</tt> - true if function f does not produce an error</li>
  111. -- <tt><li>assert_not_false(a)</tt> - true if a is not false</li>
  112. -- <tt><li>assert_not_greater_than(a, b)</tt> - true if not (a > b)</li>
  113. -- <tt><li>assert_not_gte(a, b)</tt> - true if not (a >= b)</li>
  114. -- <tt><li>assert_not_less_than(a, b)</tt> - true if not (a < b)</li>
  115. -- <tt><li>assert_not_lte(a, b)</tt> - true if not (a <= b)</li>
  116. -- <tt><li>assert_not_match(a, b)</tt> - true if the string b does not match the pattern a</li>
  117. -- <tt><li>assert_not_nil(a)</tt> - true if a is not nil</li>
  118. -- <tt><li>assert_not_true(a)</tt> - true if a is not true</li>
  119. -- <tt><li>assert_not_type(a, b)</tt> - true if a is not of type b</li>
  120. -- </ul>
  121. -- @see make_assertion
  122. -- @name assertions
  123. -- @class table
  124. local assertions = {}
  125. --- Create a custom assertion.
  126. -- This creates an assertion along with a corresponding negative assertion. It
  127. -- is used internally by telescope to create the default assertions.
  128. -- @param name The base name of the assertion.
  129. -- <p>
  130. -- The name will be used as the basis of the positive and negative assertions;
  131. -- i.e., the name <tt>equal</tt> would be used to create the assertions
  132. -- <tt>assert_equal</tt> and <tt>assert_not_equal</tt>.
  133. -- </p>
  134. -- @param message The base message that will be shown.
  135. -- <p>
  136. -- The assertion message is what is shown when the assertion fails. It will be
  137. -- prefixed with the string in <tt>telescope.assertion_message_prefix</tt>.
  138. -- The variables passed to <tt>telescope.make_assertion</tt> are interpolated
  139. -- in the message string using <tt>string.format</tt>. When creating the
  140. -- inverse assertion, the message is reused, with <tt>" to be "</tt> replaced
  141. -- by <tt>" not to be "</tt>. Hence a recommended format is something like:
  142. -- <tt>"%s to be similar to %s"</tt>.
  143. -- </p>
  144. -- @param func The assertion function itself.
  145. -- <p>
  146. -- The assertion function can have any number of arguments.
  147. -- </p>
  148. -- @usage <tt>make_assertion("equal", "%s to be equal to %s", function(a, b)
  149. -- return a == b end)</tt>
  150. -- @function make_assertion
  151. local function make_assertion(name, message, func)
  152. local num_vars = 0
  153. -- if the last vararg ends up nil, we'll need to pad the table with nils so
  154. -- that string.format gets the number of args it expects
  155. local format_message
  156. if type(message) == "function" then
  157. format_message = message
  158. else
  159. for _, _ in message:gmatch("%%s") do num_vars = num_vars + 1 end
  160. format_message = function(message, ...)
  161. local a = {}
  162. local args = {...}
  163. local nargs = select('#', ...)
  164. if nargs > num_vars then
  165. local userErrorMessage = args[num_vars+1]
  166. if type(userErrorMessage) == "string" then
  167. return(assertion_message_prefix .. userErrorMessage)
  168. else
  169. error(string.format('assert_%s expected %d arguments but got %d', name, num_vars, #args))
  170. end
  171. end
  172. for i = 1, nargs do a[i] = tostring(args[i]) end
  173. for i = nargs+1, num_vars do a[i] = 'nil' end
  174. return (assertion_message_prefix .. message):format(unpack(a))
  175. end
  176. end
  177. assertions["assert_" .. name] = function(...)
  178. if assertion_callback then assertion_callback(...) end
  179. if not func(...) then
  180. error({format_message(message, ...), debug.traceback()})
  181. end
  182. end
  183. end
  184. --- (local) Return a table with table t's values as keys and keys as values.
  185. -- @param t The table.
  186. local function invert_table(t)
  187. local t2 = {}
  188. for k, v in pairs(t) do t2[v] = k end
  189. return t2
  190. end
  191. -- (local) Truncate a string "s" to length "len", optionally followed by the
  192. -- string given in "after" if truncated; for example, truncate_string("hello
  193. -- world", 3, "...")
  194. -- @param s The string to truncate.
  195. -- @param len The desired length.
  196. -- @param after A string to append to s, if it is truncated.
  197. local function truncate_string(s, len, after)
  198. if #s <= len then
  199. return s
  200. else
  201. local s = s:sub(1, len):gsub("%s*$", '')
  202. if after then return s .. after else return s end
  203. end
  204. end
  205. --- (local) Filter a table's values by function. This function iterates over a
  206. -- table , returning only the table entries that, when passed into function f,
  207. -- yield a truthy value.
  208. -- @param t The table over which to iterate.
  209. -- @param f The filter function.
  210. local function filter(t, f)
  211. local a, b
  212. return function()
  213. repeat a, b = next(t, a)
  214. if not b then return end
  215. if f(a, b) then return a, b end
  216. until not b
  217. end
  218. end
  219. --- (local) Finds the value in the contexts table indexed with i, and returns a table
  220. -- of i's ancestor contexts.
  221. -- @param i The index in the <tt>contexts</tt> table to get ancestors for.
  222. -- @param contexts The table in which to find the ancestors.
  223. local function ancestors(i, contexts)
  224. if i == 0 then return end
  225. local a = {}
  226. local function func(j)
  227. if contexts[j].parent == 0 then return nil end
  228. table.insert(a, contexts[j].parent)
  229. func(contexts[j].parent)
  230. end
  231. func(i)
  232. return a
  233. end
  234. make_assertion("blank", "'%s' to be blank", function(a) return a == '' or a == nil end)
  235. make_assertion("empty", "'%s' to be an empty table", function(a) return not next(a) end)
  236. make_assertion("equal", "'%s' to be equal to '%s'", function(a, b) return a == b end)
  237. make_assertion("error", "result to be an error", function(f) return not pcall(f) end)
  238. make_assertion("false", "'%s' to be false", function(a) return a == false end)
  239. make_assertion("greater_than", "'%s' to be greater than '%s'", function(a, b) return a > b end)
  240. make_assertion("gte", "'%s' to be greater than or equal to '%s'", function(a, b) return a >= b end)
  241. make_assertion("less_than", "'%s' to be less than '%s'", function(a, b) return a < b end)
  242. make_assertion("lte", "'%s' to be less than or equal to '%s'", function(a, b) return a <= b end)
  243. make_assertion("match", "'%s' to be a match for %s", function(a, b) return (tostring(b)):match(a) end)
  244. make_assertion("nil", "'%s' to be nil", function(a) return a == nil end)
  245. make_assertion("true", "'%s' to be true", function(a) return a == true end)
  246. make_assertion("type", "'%s' to be a %s", function(a, b) return type(a) == b end)
  247. make_assertion("not_blank", "'%s' not to be blank", function(a) return a ~= '' and a ~= nil end)
  248. make_assertion("not_empty", "'%s' not to be an empty table", function(a) return not not next(a) end)
  249. make_assertion("not_equal", "'%s' not to be equal to '%s'", function(a, b) return a ~= b end)
  250. make_assertion("not_error", "result not to be an error", function(f) return not not pcall(f) end)
  251. make_assertion("not_match", "'%s' not to be a match for %s", function(a, b) return not (tostring(b)):match(a) end)
  252. make_assertion("not_nil", "'%s' not to be nil", function(a) return a ~= nil end)
  253. make_assertion("not_type", "'%s' not to be a %s", function(a, b) return type(a) ~= b end)
  254. --- Build a contexts table from the test file or function given in <tt>target</tt>.
  255. -- If the optional <tt>contexts</tt> table argument is provided, then the
  256. -- resulting contexts will be added to it.
  257. -- <p>
  258. -- The resulting contexts table's structure is as follows:
  259. -- </p>
  260. -- <code>
  261. -- {
  262. -- {parent = 0, name = "this is a context", context = true},
  263. -- {parent = 1, name = "this is a nested context", context = true},
  264. -- {parent = 2, name = "this is a test", test = function},
  265. -- {parent = 2, name = "this is another test", test = function},
  266. -- {parent = 0, name = "this is test outside any context", test = function},
  267. -- }
  268. -- </code>
  269. -- @param contexts A optional table in which to collect the resulting contexts
  270. -- and function.
  271. -- @function load_contexts
  272. local function load_contexts(target, contexts)
  273. local env = {}
  274. local current_index = 0
  275. local context_table = contexts or {}
  276. local function context_block(name, func)
  277. table.insert(context_table, {parent = current_index, name = name, context = true})
  278. local previous_index = current_index
  279. current_index = #context_table
  280. func()
  281. current_index = previous_index
  282. end
  283. local function test_block(name, func)
  284. local test_table = {name = name, parent = current_index, test = func or true}
  285. if current_index ~= 0 then
  286. test_table.context_name = context_table[current_index].name
  287. else
  288. test_table.context_name = 'top level'
  289. end
  290. table.insert(context_table, test_table)
  291. end
  292. local function before_block(func)
  293. context_table[current_index].before = func
  294. end
  295. local function after_block(func)
  296. context_table[current_index].after = func
  297. end
  298. for _, v in ipairs(after_aliases) do env[v] = after_block end
  299. for _, v in ipairs(before_aliases) do env[v] = before_block end
  300. for _, v in ipairs(context_aliases) do env[v] = context_block end
  301. for _, v in ipairs(test_aliases) do env[v] = test_block end
  302. -- Set these functions in the module's meta table to allow accessing
  303. -- telescope's test and context functions without env tricks. This will
  304. -- however add tests to a context table used inside the module, so multiple
  305. -- test files will add tests to the same top-level context, which may or may
  306. -- not be desired.
  307. setmetatable(_M, {__index = env})
  308. setmetatable(env, {__index = _G})
  309. local func, err = type(target) == 'string' and assert(loadfile(target)) or target
  310. if err then error(err) end
  311. setfenv(func, env)()
  312. return context_table
  313. end
  314. -- in-place table reverse.
  315. function table.reverse(t)
  316. local len = #t+1
  317. for i=1, (len-1)/2 do
  318. t[i], t[len-i] = t[len-i], t[i]
  319. end
  320. end
  321. --- Run all tests.
  322. -- This function will exectute each function in the contexts table.
  323. -- @param contexts The contexts created by <tt>load_contexts</tt>.
  324. -- @param callbacks A table of callback functions to be invoked before or after
  325. -- various test states.
  326. -- <p>
  327. -- There is a callback for each test <tt>status_code</tt>, and callbacks to run
  328. -- before or after each test invocation regardless of outcome.
  329. -- </p>
  330. -- <ul>
  331. -- <li>after - will be invoked after each test</li>
  332. -- <li>before - will be invoked before each test</li>
  333. -- <li>err - will be invoked after each test which results in an error</li>
  334. -- <li>fail - will be invoked after each failing test</li>
  335. -- <li>pass - will be invoked after each passing test</li>
  336. -- <li>pending - will be invoked after each pending test</li>
  337. -- <li>unassertive - will be invoked after each test which doesn't assert
  338. -- anything</li>
  339. -- </ul>
  340. -- <p>
  341. -- Callbacks can be used, for example, to drop into a debugger upon a failed
  342. -- assertion or error, for profiling, or updating a GUI progress meter.
  343. -- </p>
  344. -- @param test_filter A function to filter tests that match only conditions that you specify.
  345. -- <p>
  346. -- For example, the folling would allow you to run only tests whose name matches a pattern:
  347. -- </p>
  348. -- <p>
  349. -- <code>
  350. -- function(t) return t.name:match("%s* lexer") end
  351. -- </code>
  352. -- </p>
  353. -- @return A table of result tables. Each result table has the following
  354. -- fields:
  355. -- <ul>
  356. -- <li>assertions_invoked - the number of assertions the test invoked</li>
  357. -- <li>context - the name of the context</li>
  358. -- <li>message - a table with an error message and stack trace</li>
  359. -- <li>name - the name of the test</li>
  360. -- <li>status_code - the resulting status code</li>
  361. -- <li>status_label - the label for the status_code</li>
  362. -- </ul>
  363. -- @see load_contexts
  364. -- @see status_codes
  365. -- @function run
  366. local function run(contexts, callbacks, test_filter)
  367. local results = {}
  368. local status_names = invert_table(status_codes)
  369. local test_filter = test_filter or function(a) return a end
  370. -- Setup a new environment suitable for running a new test
  371. local function newEnv()
  372. local env = {}
  373. -- Make sure globals are accessible in the new environment
  374. setmetatable(env, {__index = _G})
  375. -- Setup all the assert functions in the new environment
  376. for k, v in pairs(assertions) do
  377. setfenv(v, env)
  378. env[k] = v
  379. end
  380. return env
  381. end
  382. local env = newEnv()
  383. local function invoke_callback(name, test)
  384. if not callbacks then return end
  385. if type(callbacks[name]) == "table" then
  386. for _, c in ipairs(callbacks[name]) do c(test) end
  387. elseif callbacks[name] then
  388. callbacks[name](test)
  389. end
  390. end
  391. local function invoke_test(func)
  392. local assertions_invoked = 0
  393. env.assertion_callback = function()
  394. assertions_invoked = assertions_invoked + 1
  395. end
  396. setfenv(func, env)
  397. local result, message = xpcall(func, debug.traceback)
  398. if result and assertions_invoked > 0 then
  399. return status_codes.pass, assertions_invoked, nil
  400. elseif result then
  401. return status_codes.unassertive, 0, nil
  402. elseif type(message) == "table" then
  403. return status_codes.fail, assertions_invoked, message
  404. else
  405. return status_codes.err, assertions_invoked, {message, debug.traceback()}
  406. end
  407. end
  408. for i, v in filter(contexts, function(i, v) return v.test and test_filter(v) end) do
  409. env = newEnv() -- Setup a new environment for this test
  410. local ancestors = ancestors(i, contexts)
  411. local context_name = 'Top level'
  412. if contexts[i].parent ~= 0 then
  413. context_name = contexts[contexts[i].parent].name
  414. end
  415. local result = {
  416. assertions_invoked = 0,
  417. name = contexts[i].name,
  418. context = context_name,
  419. test = i
  420. }
  421. table.sort(ancestors)
  422. -- this "before" is the test callback passed into the runner
  423. invoke_callback("before", result)
  424. -- run all the "before" blocks/functions
  425. for _, a in ipairs(ancestors) do
  426. if contexts[a].before then
  427. setfenv(contexts[a].before, env)
  428. contexts[a].before()
  429. end
  430. end
  431. -- check if it's a function because pending tests will just have "true"
  432. if type(v.test) == "function" then
  433. result.status_code, result.assertions_invoked, result.message = invoke_test(v.test)
  434. invoke_callback(status_names[result.status_code], result)
  435. else
  436. result.status_code = status_codes.pending
  437. invoke_callback("pending", result)
  438. end
  439. result.status_label = status_labels[result.status_code]
  440. -- Run all the "after" blocks/functions
  441. table.reverse(ancestors)
  442. for _, a in ipairs(ancestors) do
  443. if contexts[a].after then
  444. setfenv(contexts[a].after, env)
  445. contexts[a].after()
  446. end
  447. end
  448. invoke_callback("after", result)
  449. results[i] = result
  450. end
  451. return results
  452. end
  453. --- Return a detailed report for each context, with the status of each test.
  454. -- @param contexts The contexts returned by <tt>load_contexts</tt>.
  455. -- @param results The results returned by <tt>run</tt>.
  456. -- @function test_report
  457. local function test_report(contexts, results)
  458. local buffer = {}
  459. local leading_space = " "
  460. local level = 0
  461. local line_char = "-"
  462. local previous_level = 0
  463. local status_format_len = 3
  464. local status_format = "[%s]"
  465. local width = 72
  466. local context_name_format = "%-" .. width - status_format_len .. "s"
  467. local function_name_format = "%-" .. width - status_format_len .. "s"
  468. local function space()
  469. return leading_space:rep(level - 1)
  470. end
  471. local function add_divider()
  472. table.insert(buffer, line_char:rep(width))
  473. end
  474. add_divider()
  475. for i, item in ipairs(contexts) do
  476. local ancestors = ancestors(i, contexts)
  477. previous_level = level or 0
  478. level = #ancestors
  479. -- the 4 here is the length of "..." plus one space of padding
  480. local name = truncate_string(item.name, width - status_format_len - 4 - #ancestors, '...')
  481. if previous_level ~= level and level == 0 then add_divider() end
  482. if item.context then
  483. table.insert(buffer, context_name_format:format(space() .. name .. ':'))
  484. elseif results[i] then
  485. table.insert(buffer, function_name_format:format(space() .. name) ..
  486. status_format:format(results[i].status_label))
  487. end
  488. end
  489. add_divider()
  490. return table.concat(buffer, "\n")
  491. end
  492. --- Return a table of stack traces for tests which produced a failure or an error.
  493. -- @param contexts The contexts returned by <tt>load_contexts</tt>.
  494. -- @param results The results returned by <tt>run</tt>.
  495. -- @function error_report
  496. local function error_report(contexts, results)
  497. local buffer = {}
  498. for _, r in filter(results, function(i, r) return r.message end) do
  499. local name = contexts[r.test].name
  500. table.insert(buffer, name .. ":\n" .. r.message[1] .. "\n" .. r.message[2])
  501. end
  502. if #buffer > 0 then return table.concat(buffer, "\n") end
  503. end
  504. --- Get a one-line report and a summary table with the status counts. The
  505. -- counts given are: total tests, assertions, passed tests, failed tests,
  506. -- pending tests, and tests which didn't assert anything.
  507. -- @return A report that can be printed
  508. -- @return A table with the various counts. Its fields are:
  509. -- <tt>assertions</tt>, <tt>errors</tt>, <tt>failed</tt>, <tt>passed</tt>,
  510. -- <tt>pending</tt>, <tt>tests</tt>, <tt>unassertive</tt>.
  511. -- @param contexts The contexts returned by <tt>load_contexts</tt>.
  512. -- @param results The results returned by <tt>run</tt>.
  513. -- @function summary_report
  514. local function summary_report(contexts, results)
  515. local r = {
  516. assertions = 0,
  517. errors = 0,
  518. failed = 0,
  519. passed = 0,
  520. pending = 0,
  521. tests = 0,
  522. unassertive = 0
  523. }
  524. for _, v in pairs(results) do
  525. r.tests = r.tests + 1
  526. r.assertions = r.assertions + v.assertions_invoked
  527. if v.status_code == status_codes.err then r.errors = r.errors + 1
  528. elseif v.status_code == status_codes.fail then r.failed = r.failed + 1
  529. elseif v.status_code == status_codes.pass then r.passed = r.passed + 1
  530. elseif v.status_code == status_codes.pending then r.pending = r.pending + 1
  531. elseif v.status_code == status_codes.unassertive then r.unassertive = r.unassertive + 1
  532. end
  533. end
  534. local buffer = {}
  535. for _, k in ipairs({"tests", "passed", "assertions", "failed", "errors", "unassertive", "pending"}) do
  536. local number = r[k]
  537. local label = k
  538. if number == 1 then
  539. label = label:gsub("s$", "")
  540. end
  541. table.insert(buffer, ("%d %s"):format(number, label))
  542. end
  543. return table.concat(buffer, " "), r
  544. end
  545. _M.after_aliases = after_aliases
  546. _M.make_assertion = make_assertion
  547. _M.assertion_message_prefix = assertion_message_prefix
  548. _M.before_aliases = before_aliases
  549. _M.context_aliases = context_aliases
  550. _M.error_report = error_report
  551. _M.load_contexts = load_contexts
  552. _M.run = run
  553. _M.test_report = test_report
  554. _M.status_codes = status_codes
  555. _M.status_labels = status_labels
  556. _M.summary_report = summary_report
  557. _M.test_aliases = test_aliases
  558. _M.version = _VERSION
  559. _M._VERSION = _VERSION
  560. return _M