root / trunk / ext / lua / lunit.lua

Revision 217, 17.8 kB (checked in by kapheine, 3 years ago)

Initial import of Lua extension

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1
2--[[--------------------------------------------------------------------------
3
4    This file is part of lunit 0.3 (alpha).
5
6    For Details about lunit look at: http://www.nessie.de/mroth/lunit/
7
8    Author: Michael Roth <mroth@nessie.de>
9
10    Copyright (c) 2004 Michael Roth <mroth@nessie.de>
11
12    Permission is hereby granted, free of charge, to any person
13    obtaining a copy of this software and associated documentation
14    files (the "Software"), to deal in the Software without restriction,
15    including without limitation the rights to use, copy, modify, merge,
16    publish, distribute, sublicense, and/or sell copies of the Software,
17    and to permit persons to whom the Software is furnished to do so,
18    subject to the following conditions:
19
20    The above copyright notice and this permission notice shall be
21    included in all copies or substantial portions of the Software.
22
23    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
26    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
27    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
28    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
29    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30
31--]]--------------------------------------------------------------------------
32
33
34
35
36-----------------------
37-- Intialize package --
38-----------------------
39
40local P = { }
41lunit = P
42
43-- Import
44local type = type
45local print = print
46local ipairs = ipairs
47local pairs = pairs
48local string = string
49local table = table
50local pcall = pcall
51local xpcall = xpcall
52local traceback = debug.traceback
53local error = error
54local setmetatable = setmetatable
55local rawset = rawset
56local orig_assert = assert
57local getfenv = getfenv
58local setfenv = setfenv
59local tostring = tostring
60
61
62-- Start package scope
63setfenv(1, P)
64
65
66
67
68--------------------------------
69-- Private data and functions --
70--------------------------------
71
72local run_testcase
73local do_assert, check_msg
74local stats = { }
75local testcases = { }
76local stats_inc, tc_mt
77
78
79
80
81--------------------------
82-- Type check functions --
83--------------------------
84
85function is_nil(x)
86  return type(x) == "nil"
87end
88
89function is_boolean(x)
90  return type(x) == "boolean"
91end
92
93function is_number(x)
94  return type(x) == "number"
95end
96
97function is_string(x)
98  return type(x) == "string"
99end
100
101function is_table(x)
102  return type(x) == "table"
103end
104
105function is_function(x)
106  return type(x) == "function"
107end
108
109function is_thread(x)
110  return type(x) == "thread"
111end
112
113function is_userdata(x)
114  return type(x) == "userdata"
115end
116
117
118
119
120----------------------
121-- Assert functions --
122----------------------
123
124function assert(assertion, msg)
125  stats_inc("assertions")
126  check_msg("assert", msg)
127  do_assert(not not assertion, "assertion failed (was: "..tostring(assertion)..")", msg)                -- (convert assertion to bool)
128  return assertion
129end
130
131
132function assert_fail(msg)
133  stats_inc("assertions")
134  check_msg("assert_fail", msg)
135  do_assert(false, "failure", msg)
136end
137
138
139function assert_true(actual, msg)
140  stats_inc("assertions")
141  check_msg("assert_true", msg)
142  do_assert(is_boolean(actual), "true expected but was a "..type(actual), msg)
143  do_assert(actual == true, "true expected but was false", msg)
144  return actual
145end
146
147
148function assert_false(actual, msg)
149  stats_inc("assertions")
150  check_msg("assert_false", msg)
151  do_assert(is_boolean(actual), "false expected but was a "..type(actual), msg)
152  do_assert(actual == false, "false expected but was true", msg)
153  return actual
154end
155
156
157function assert_equal(expected, actual, msg)
158  stats_inc("assertions")
159  check_msg("assert_equal", msg)
160  do_assert(expected == actual, "expected '"..tostring(expected).."' but was '"..tostring(actual).."'", msg)
161  return actual
162end
163
164
165function assert_not_equal(unexpected, actual, msg)
166  stats_inc("assertions")
167  check_msg("assert_not_equal", msg)
168  do_assert(unexpected ~= actual, "'"..tostring(expected).."' not expected but was one", msg)
169  return actual
170end
171
172
173function assert_match(pattern, actual, msg)
174  stats_inc("assertions")
175  check_msg("assert_match", msg)
176  do_assert(is_string(pattern), "assert_match expects the pattern as a string")
177  do_assert(is_string(actual), "expected a string to match pattern '"..pattern.."' but was a '"..type(actual).."'", msg)
178  do_assert(not not string.find(actual, pattern), "expected '"..actual.."' to match pattern '"..pattern.."' but doesn't", msg)
179  return actual
180end
181
182
183function assert_not_match(pattern, actual, msg)
184  stats_inc("assertions")
185  check_msg("assert_not_match", msg)
186  do_assert(is_string(actual), "expected a string to not match pattern '"..pattern.."' but was a '"..type(actual).."'", msg)
187  do_assert(string.find(actual, pattern) == nil, "expected '"..actual.."' to not match pattern '"..pattern.."' but it does", msg)
188  return actual
189end
190
191
192function assert_nil(actual, msg)
193  stats_inc("assertions")
194  check_msg("assert_nil", msg)
195  do_assert(is_nil(actual), "nil expected but was a "..type(actual), msg)
196  return actual
197end
198
199
200function assert_not_nil(actual, msg)
201  stats_inc("assertions")
202  check_msg("assert_not_nil", msg)
203  do_assert(not is_nil(actual), "nil not expected but was one", msg)
204  return actual
205end
206
207
208function assert_boolean(actual, msg)
209  stats_inc("assertions")
210  check_msg("assert_boolean", msg)
211  do_assert(is_boolean(actual), "boolean expected but was a "..type(actual), msg)
212  return actual
213end
214
215
216function assert_not_boolean(actual, msg)
217  stats_inc("assertions")
218  check_msg("assert_not_boolean", msg)
219  do_assert(not is_boolean(actual), "boolean not expected but was one", msg)
220  return actual
221end
222
223
224function assert_number(actual, msg)
225  stats_inc("assertions")
226  check_msg("assert_number", msg)
227  do_assert(is_number(actual), "number expected but was a "..type(actual), msg)
228  return actual
229end
230
231
232function assert_not_number(actual, msg)
233  stats_inc("assertions")
234  check_msg("assert_not_number", msg)
235  do_assert(not is_number(actual), "number not expected but was one", msg)
236  return actual
237end
238
239
240function assert_string(actual, msg)
241  stats_inc("assertions")
242  check_msg("assert_string", msg)
243  do_assert(is_string(actual), "string expected but was a "..type(actual), msg)
244  return actual
245end
246
247
248function assert_not_string(actual, msg)
249  stats_inc("assertions")
250  check_msg("assert_not_string", msg)
251  do_assert(not is_string(actual), "string not expected but was one", msg)
252  return actual
253end
254
255
256function assert_table(actual, msg)
257  stats_inc("assertions")
258  check_msg("assert_table", msg)
259  do_assert(is_table(actual), "table expected but was a "..type(actual), msg)
260  return actual
261end
262
263
264function assert_not_table(actual, msg)
265  stats_inc("assertions")
266  check_msg("assert_not_table", msg)
267  do_assert(not is_table(actual), "table not expected but was one", msg)
268  return actual
269end
270
271
272function assert_function(actual, msg)
273  stats_inc("assertions")
274  check_msg("assert_function", msg)
275  do_assert(is_function(actual), "function expected but was a "..type(actual), msg)
276  return actual
277end
278
279
280function assert_not_function(actual, msg)
281  stats_inc("assertions")
282  check_msg("assert_not_function", msg)
283  do_assert(not is_function(actual), "function not expected but was one", msg)
284  return actual
285end
286
287
288function assert_thread(actual, msg)
289  stats_inc("assertions")
290  check_msg("assert_thread", msg)
291  do_assert(is_thread(actual), "thread expected but was a "..type(actual), msg)
292  return actual
293end
294
295
296function assert_not_thread(actual, msg)
297  stats_inc("assertions")
298  check_msg("assert_not_thread", msg)
299  do_assert(not is_thread(actual), "thread not expected but was one", msg)
300  return actual
301end
302
303
304function assert_userdata(actual, msg)
305  stats_inc("assertions")
306  check_msg("assert_userdata", msg)
307  do_assert(is_userdata(actual), "userdata expected but was a "..type(actual), msg)
308  return actual
309end
310
311
312function assert_not_userdata(actual, msg)
313  stats_inc("assertions")
314  check_msg("assert_not_userdata", msg)
315  do_assert(not is_userdata(actual), "userdata not expected but was one", msg)
316  return actual
317end
318
319
320function assert_error(msg, func)
321  stats_inc("assertions")
322  if is_nil(func) then func, msg = msg, nil end
323  check_msg("assert_error", msg)
324  do_assert(is_function(func), "assert_error expects a function as the last argument but it was a "..type(func))
325  local ok, errmsg = pcall(func)
326  do_assert(ok == false, "error expected but no error occurred", msg)
327end
328
329
330function assert_error_match(msg, ret, func)
331    stats_inc("assertions")
332    if is_nil(func) then msg, ret, func = nil, msg, ret end
333    check_msg("assert_error", msg)
334    do_assert(is_function(func), "assert_error expects a function as the last argument but it was a "..type(func))
335    local ok, errmsg = pcall(func)
336    do_assert(ok == false, "error expected but no error occurred", msg)
337    do_assert(string.find(errmsg, ret) ~= nil, "error return code was not correct: `"..errmsg.."`", msg)
338end
339
340function assert_pass(msg, func)
341  stats_inc("assertions")
342  if is_nil(func) then func, msg = msg, nil end
343  check_msg("assert_pass", msg)
344  do_assert(is_function(func), "assert_pass expects a function as the last argument but it was a "..type(func))
345  local ok, errmsg = pcall(func)
346  if not ok then do_assert(ok == true, "no error expected but error was: "..errmsg, msg) end
347end
348
349
350
351
352-----------------------------------------------------------
353-- Assert implementation that assumes it was called from --
354-- lunit code which was called directly from user code.  --
355-----------------------------------------------------------
356
357function do_assert(assertion, base_msg, user_msg)
358  orig_assert(is_boolean(assertion))
359  orig_assert(is_string(base_msg))
360  orig_assert(is_string(user_msg) or is_nil(user_msg))
361  if not assertion then
362    if user_msg then
363      error(base_msg..": "..user_msg, 3)
364    else
365      error(base_msg.."!", 3)
366    end
367  end
368end
369
370-------------------------------------------
371-- Checks the msg argument in assert_xxx --
372-------------------------------------------
373
374function check_msg(name, msg)
375  orig_assert(is_string(name))
376  if not (is_nil(msg) or is_string(msg)) then
377    error("lunit."..name.."() expects the optional message as a string but it was a "..type(msg).."!" ,3)
378  end
379end
380
381
382
383
384-------------------------------------
385-- Creates a new TestCase 'Object' --
386-------------------------------------
387
388function TestCase(name)
389  do_assert(is_string(name), "lunit.TestCase() needs a string as an argument")
390  local tc = {
391    __lunit_name = name;
392    __lunit_setup = nil;
393    __lunit_tests = { };
394    __lunit_teardown = nil;
395  }
396  setmetatable(tc, tc_mt)
397  table.insert(testcases, tc)
398  return tc
399end
400
401tc_mt = {
402  __newindex = function(tc, key, value)
403    rawset(tc, key, value)
404    if is_string(key) and is_function(value) then
405      local name = string.lower(key)
406      if string.find(name, "^test") or string.find(name, "test$") then
407        table.insert(tc.__lunit_tests, key)
408      elseif name == "setup" then
409        tc.__lunit_setup = value
410      elseif name == "teardown" then
411        tc.__lunit_teardown = value
412      end
413    end
414  end
415}
416
417
418
419-----------------------------------------
420-- Wrap Functions in a TestCase object --
421-----------------------------------------
422
423function wrap(name, ...)
424  if is_function(name) then
425    table.insert(arg, 1, name)
426    name = "Anonymous Testcase"
427  end
428 
429  local tc = TestCase(name)
430  for index, test in ipairs(arg) do
431    tc["Test #"..tostring(index)] = test
432  end
433  return tc
434end
435
436
437
438
439
440
441----------------------------------
442-- Runs the complete Test Suite --
443----------------------------------
444
445function run()
446 
447  ---------------------------
448  -- Initialize statistics --
449  ---------------------------
450 
451  stats.testcases = 0   -- Total number of Test Cases
452  stats.tests = 0       -- Total number of all Tests in all Test Cases
453  stats.run = 0         -- Number of Tests run
454  stats.notrun = 0      -- Number of Tests not run
455  stats.failed = 0      -- Number of Tests failed
456  stats.passed = 0      -- Number of Test passed
457  stats.assertions = 0  -- Number of all assertions made in all Test in all Test Cases
458 
459  --------------------------------
460  -- Count Test Cases and Tests --
461  --------------------------------
462 
463  stats.testcases = table.getn(testcases)
464 
465  for _, tc in ipairs(testcases) do
466    stats_inc("tests" , table.getn(tc.__lunit_tests))
467  end
468 
469  ------------------
470  -- Print Header --
471  ------------------
472 
473  print()
474  print("#### Test Suite with "..stats.tests.." Tests in "..stats.testcases.." Test Cases loaded.")
475 
476  ------------------------
477  -- Run all Test Cases --
478  ------------------------
479 
480  for _, tc in ipairs(testcases) do
481    run_testcase(tc)
482  end
483 
484  ------------------
485  -- Print Footer --
486  ------------------
487 
488  print()
489  print("#### Test Suite finished.")
490 
491  local msg_assertions = stats.assertions.." Assertions checked. "
492  local msg_passed     = stats.passed == stats.tests and "All Tests passed" or  stats.passed.." Tests passed"
493  local msg_failed     = stats.failed > 0 and ", "..stats.failed.." failed" or ""
494  local msg_run        = stats.notrun > 0 and ", "..stats.notrun.." not run" or ""
495 
496  print()
497  print(msg_assertions..msg_passed..msg_failed..msg_run.."!")
498 
499  -----------------
500  -- Return code --
501  -----------------
502 
503  if stats.passed == stats.tests then
504    return 0
505  else
506    return 1
507  end
508end
509
510
511
512
513-----------------------------
514-- Runs a single Test Case --
515-----------------------------
516
517function run_testcase(tc)
518 
519  orig_assert(is_table(tc))
520  orig_assert(is_table(tc.__lunit_tests))
521  orig_assert(is_string(tc.__lunit_name))
522  orig_assert(is_nil(tc.__lunit_setup) or is_function(tc.__lunit_setup))
523  orig_assert(is_nil(tc.__lunit_teardown) or is_function(tc.__lunit_teardown))
524 
525  --------------------------------------------
526  -- Protected call to a Test Case function --
527  --------------------------------------------
528 
529  local function call(errprefix, func)
530    orig_assert(is_string(errprefix))
531    orig_assert(is_function(func))
532    local ok, errmsg = xpcall(function() func(tc) end, traceback)
533    if not ok then
534      print()
535      print(errprefix..": "..errmsg)
536    end
537    return ok
538  end
539 
540  ------------------------------------
541  -- Calls setup() on the Test Case --
542  ------------------------------------
543 
544  local function setup()
545    if tc.__lunit_setup then
546      return call("ERROR: setup() failed", tc.__lunit_setup)
547    else
548      return true
549    end
550  end
551 
552  ------------------------------------------
553  -- Calls a single Test on the Test Case --
554  ------------------------------------------
555 
556  local function run(testname)
557    orig_assert(is_string(testname))
558    orig_assert(is_function(tc[testname]))
559    local ok = call("FAIL: "..testname, tc[testname])
560    if not ok then
561      stats_inc("failed")
562    else
563      stats_inc("passed")
564    end
565    return ok
566  end
567 
568  ---------------------------------------
569  -- Calls teardown() on the Test Case --
570  ---------------------------------------
571 
572  local function teardown()
573     if tc.__lunit_teardown then
574       call("WARNING: teardown() failed", tc.__lunit_teardown)
575     end
576  end
577 
578  ---------------------------------
579  -- Run all Tests on a TestCase --
580  ---------------------------------
581 
582  print()
583  print("#### Running '"..tc.__lunit_name.."' ("..table.getn(tc.__lunit_tests).." Tests)...")
584 
585  for _, testname in ipairs(tc.__lunit_tests) do
586    if setup() then
587      run(testname)
588      stats_inc("run")
589      teardown()
590    else
591      print("WARN: Skipping '"..testname.."'...")
592      stats_inc("notrun")
593    end
594  end
595 
596end
597
598
599
600
601---------------------
602-- Import function --
603---------------------
604
605function import(name)
606 
607  do_assert(is_string(name), "lunit.import() expects a single string as argument")
608 
609  local user_env = getfenv(2)
610 
611  --------------------------------------------------
612  -- Installs a specific function in the user env --
613  --------------------------------------------------
614 
615  local function install(funcname)
616    user_env[funcname] = P[funcname]
617  end
618 
619 
620  ----------------------------------------------------------
621  -- Install functions matching a pattern in the user env --
622  ----------------------------------------------------------
623 
624  local function install_pattern(pattern)
625    for funcname, _ in pairs(P) do
626      if string.find(funcname, pattern) then
627        install(funcname)
628      end
629    end
630  end
631 
632  ------------------------------------------------------------
633  -- Installs assert() and all assert_xxx() in the user env --
634  ------------------------------------------------------------
635 
636  local function install_asserts()
637    install_pattern("^assert.*")
638  end
639 
640  -------------------------------------------
641  -- Installs all is_xxx() in the user env --
642  -------------------------------------------
643 
644  local function install_tests()
645    install_pattern("^is_.+")
646  end
647 
648  if name == "asserts" or name == "assertions" then
649    install_asserts()
650  elseif name == "tests" or name == "checks" then
651    install_tests()
652  elseif name == "all" then
653    install_asserts()
654    install_tests()
655    install("TestCase")
656  elseif string.find(name, "^assert.*") and P[name] then
657    install(name)
658  elseif string.find(name, "^is_.+") and P[name] then
659    install(name)
660  elseif name == "TestCase" then
661    install("TestCase")
662  else
663    error("luniit.import(): invalid function '"..name.."' to import", 2)
664  end
665end
666
667
668
669
670--------------------------------------------------
671-- Installs a private environment on the caller --
672--------------------------------------------------
673
674function setprivfenv()
675  local new_env = { }
676  local new_env_mt = { __index = getfenv(2) }
677  setmetatable(new_env, new_env_mt)
678  setfenv(2, new_env)
679end
680
681
682
683
684--------------------------------------------------
685-- Increments a counter in the statistics table -- 
686--------------------------------------------------
687
688function stats_inc(varname, value)
689  orig_assert(is_table(stats))
690  orig_assert(is_string(varname))
691  orig_assert(is_nil(value) or is_number(value))
692  if not stats[varname] then return end
693  stats[varname] = stats[varname] + (value or 1)
694end
695
696
697
698
Note: See TracBrowser for help on using the browser.