test/lunit.lua

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