|
1 |
|
2 --[[-------------------------------------------------------------------------- |
|
3 |
|
4 This file is part of lunit 0.4pre (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 |
|
40 local P = { } |
|
41 lunit = P |
|
42 |
|
43 -- Import |
|
44 local type = type |
|
45 local print = print |
|
46 local ipairs = ipairs |
|
47 local pairs = pairs |
|
48 local string = string |
|
49 local table = table |
|
50 local pcall = pcall |
|
51 local xpcall = xpcall |
|
52 local traceback = debug.traceback |
|
53 local error = error |
|
54 local setmetatable = setmetatable |
|
55 local rawset = rawset |
|
56 local orig_assert = assert |
|
57 local getfenv = getfenv |
|
58 local setfenv = setfenv |
|
59 local tostring = tostring |
|
60 |
|
61 |
|
62 -- Start package scope |
|
63 setfenv(1, P) |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 -------------------------------- |
|
69 -- Private data and functions -- |
|
70 -------------------------------- |
|
71 |
|
72 local run_testcase |
|
73 local do_assert, check_msg |
|
74 local stats = { } |
|
75 local testcases = { } |
|
76 local stats_inc, tc_mt |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 -------------------------- |
|
82 -- Type check functions -- |
|
83 -------------------------- |
|
84 |
|
85 function is_nil(x) |
|
86 return type(x) == "nil" |
|
87 end |
|
88 |
|
89 function is_boolean(x) |
|
90 return type(x) == "boolean" |
|
91 end |
|
92 |
|
93 function is_number(x) |
|
94 return type(x) == "number" |
|
95 end |
|
96 |
|
97 function is_string(x) |
|
98 return type(x) == "string" |
|
99 end |
|
100 |
|
101 function is_table(x) |
|
102 return type(x) == "table" |
|
103 end |
|
104 |
|
105 function is_function(x) |
|
106 return type(x) == "function" |
|
107 end |
|
108 |
|
109 function is_thread(x) |
|
110 return type(x) == "thread" |
|
111 end |
|
112 |
|
113 function is_userdata(x) |
|
114 return type(x) == "userdata" |
|
115 end |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 ---------------------- |
|
121 -- Assert functions -- |
|
122 ---------------------- |
|
123 |
|
124 function 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 |
|
129 end |
|
130 |
|
131 |
|
132 function assert_fail(msg) |
|
133 stats_inc("assertions") |
|
134 check_msg("assert_fail", msg) |
|
135 do_assert(false, "failure", msg) |
|
136 end |
|
137 |
|
138 |
|
139 function 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 |
|
145 end |
|
146 |
|
147 |
|
148 function 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 |
|
154 end |
|
155 |
|
156 |
|
157 function 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 |
|
162 end |
|
163 |
|
164 |
|
165 function 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 |
|
170 end |
|
171 |
|
172 |
|
173 function 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 |
|
180 end |
|
181 |
|
182 |
|
183 function 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 |
|
189 end |
|
190 |
|
191 |
|
192 function 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 |
|
197 end |
|
198 |
|
199 |
|
200 function 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 |
|
205 end |
|
206 |
|
207 |
|
208 function 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 |
|
213 end |
|
214 |
|
215 |
|
216 function 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 |
|
221 end |
|
222 |
|
223 |
|
224 function 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 |
|
229 end |
|
230 |
|
231 |
|
232 function 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 |
|
237 end |
|
238 |
|
239 |
|
240 function 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 |
|
245 end |
|
246 |
|
247 |
|
248 function 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 |
|
253 end |
|
254 |
|
255 |
|
256 function 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 |
|
261 end |
|
262 |
|
263 |
|
264 function 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 |
|
269 end |
|
270 |
|
271 |
|
272 function 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 |
|
277 end |
|
278 |
|
279 |
|
280 function 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 |
|
285 end |
|
286 |
|
287 |
|
288 function 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 |
|
293 end |
|
294 |
|
295 |
|
296 function 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 |
|
301 end |
|
302 |
|
303 |
|
304 function 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 |
|
309 end |
|
310 |
|
311 |
|
312 function 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 |
|
317 end |
|
318 |
|
319 |
|
320 function 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) |
|
327 end |
|
328 |
|
329 |
|
330 function assert_pass(msg, func) |
|
331 stats_inc("assertions") |
|
332 if is_nil(func) then func, msg = msg, nil end |
|
333 check_msg("assert_pass", msg) |
|
334 do_assert(is_function(func), "assert_pass expects a function as the last argument but it was a "..type(func)) |
|
335 local ok, errmsg = pcall(func) |
|
336 if not ok then do_assert(ok == true, "no error expected but error was: "..errmsg, msg) end |
|
337 end |
|
338 |
|
339 |
|
340 |
|
341 |
|
342 ----------------------------------------------------------- |
|
343 -- Assert implementation that assumes it was called from -- |
|
344 -- lunit code which was called directly from user code. -- |
|
345 ----------------------------------------------------------- |
|
346 |
|
347 function do_assert(assertion, base_msg, user_msg) |
|
348 orig_assert(is_boolean(assertion)) |
|
349 orig_assert(is_string(base_msg)) |
|
350 orig_assert(is_string(user_msg) or is_nil(user_msg)) |
|
351 if not assertion then |
|
352 if user_msg then |
|
353 error(base_msg..": "..user_msg, 3) |
|
354 else |
|
355 error(base_msg.."!", 3) |
|
356 end |
|
357 end |
|
358 end |
|
359 |
|
360 ------------------------------------------- |
|
361 -- Checks the msg argument in assert_xxx -- |
|
362 ------------------------------------------- |
|
363 |
|
364 function check_msg(name, msg) |
|
365 orig_assert(is_string(name)) |
|
366 if not (is_nil(msg) or is_string(msg)) then |
|
367 error("lunit."..name.."() expects the optional message as a string but it was a "..type(msg).."!" ,3) |
|
368 end |
|
369 end |
|
370 |
|
371 |
|
372 |
|
373 |
|
374 ------------------------------------- |
|
375 -- Creates a new TestCase 'Object' -- |
|
376 ------------------------------------- |
|
377 |
|
378 function TestCase(name) |
|
379 do_assert(is_string(name), "lunit.TestCase() needs a string as an argument") |
|
380 local tc = { |
|
381 __lunit_name = name; |
|
382 __lunit_setup = nil; |
|
383 __lunit_tests = { }; |
|
384 __lunit_teardown = nil; |
|
385 } |
|
386 setmetatable(tc, tc_mt) |
|
387 table.insert(testcases, tc) |
|
388 return tc |
|
389 end |
|
390 |
|
391 tc_mt = { |
|
392 __newindex = function(tc, key, value) |
|
393 rawset(tc, key, value) |
|
394 if is_string(key) and is_function(value) then |
|
395 local name = string.lower(key) |
|
396 if string.find(name, "^test") or string.find(name, "test$") then |
|
397 table.insert(tc.__lunit_tests, key) |
|
398 elseif name == "setup" then |
|
399 tc.__lunit_setup = value |
|
400 elseif name == "teardown" then |
|
401 tc.__lunit_teardown = value |
|
402 end |
|
403 end |
|
404 end |
|
405 } |
|
406 |
|
407 |
|
408 |
|
409 ----------------------------------------- |
|
410 -- Wrap Functions in a TestCase object -- |
|
411 ----------------------------------------- |
|
412 |
|
413 function wrap(name, ...) |
|
414 if is_function(name) then |
|
415 table.insert(arg, 1, name) |
|
416 name = "Anonymous Testcase" |
|
417 end |
|
418 |
|
419 local tc = TestCase(name) |
|
420 for index, test in ipairs(arg) do |
|
421 tc["Test #"..index] = test |
|
422 end |
|
423 return tc |
|
424 end |
|
425 |
|
426 |
|
427 |
|
428 |
|
429 |
|
430 |
|
431 ---------------------------------- |
|
432 -- Runs the complete Test Suite -- |
|
433 ---------------------------------- |
|
434 |
|
435 function run() |
|
436 |
|
437 --------------------------- |
|
438 -- Initialize statistics -- |
|
439 --------------------------- |
|
440 |
|
441 stats.testcases = 0 -- Total number of Test Cases |
|
442 stats.tests = 0 -- Total number of all Tests in all Test Cases |
|
443 stats.run = 0 -- Number of Tests run |
|
444 stats.notrun = 0 -- Number of Tests not run |
|
445 stats.failed = 0 -- Number of Tests failed |
|
446 stats.warnings = 0 -- Number of Warnings (teardown) |
|
447 stats.errors = 0 -- Number of Errors (setup) |
|
448 stats.passed = 0 -- Number of Test passed |
|
449 stats.assertions = 0 -- Number of all assertions made in all Test in all Test Cases |
|
450 |
|
451 -------------------------------- |
|
452 -- Count Test Cases and Tests -- |
|
453 -------------------------------- |
|
454 |
|
455 stats.testcases = table.getn(testcases) |
|
456 |
|
457 for _, tc in ipairs(testcases) do |
|
458 stats_inc("tests" , table.getn(tc.__lunit_tests)) |
|
459 end |
|
460 |
|
461 ------------------ |
|
462 -- Print Header -- |
|
463 ------------------ |
|
464 |
|
465 print() |
|
466 print("#### Test Suite with "..stats.tests.." Tests in "..stats.testcases.." Test Cases loaded.") |
|
467 |
|
468 ------------------------ |
|
469 -- Run all Test Cases -- |
|
470 ------------------------ |
|
471 |
|
472 for _, tc in ipairs(testcases) do |
|
473 run_testcase(tc) |
|
474 end |
|
475 |
|
476 ------------------ |
|
477 -- Print Footer -- |
|
478 ------------------ |
|
479 |
|
480 print() |
|
481 print("#### Test Suite finished.") |
|
482 |
|
483 local msg_assertions = stats.assertions.." Assertions checked. " |
|
484 local msg_passed = stats.passed == stats.tests and "All Tests passed" or stats.passed.." Tests passed" |
|
485 local msg_failed = stats.failed > 0 and ", "..stats.failed.." failed" or "" |
|
486 local msg_run = stats.notrun > 0 and ", "..stats.notrun.." not run" or "" |
|
487 local msg_warn = stats.warnings > 0 and ", "..stats.warnings.." warnings" or "" |
|
488 |
|
489 print() |
|
490 print(msg_assertions..msg_passed..msg_failed..msg_run..msg_warn.."!") |
|
491 |
|
492 ----------------- |
|
493 -- Return code -- |
|
494 ----------------- |
|
495 |
|
496 if stats.passed == stats.tests then |
|
497 return 0 |
|
498 else |
|
499 return 1 |
|
500 end |
|
501 end |
|
502 |
|
503 |
|
504 |
|
505 |
|
506 ----------------------------- |
|
507 -- Runs a single Test Case -- |
|
508 ----------------------------- |
|
509 |
|
510 function run_testcase(tc) |
|
511 |
|
512 orig_assert(is_table(tc)) |
|
513 orig_assert(is_table(tc.__lunit_tests)) |
|
514 orig_assert(is_string(tc.__lunit_name)) |
|
515 orig_assert(is_nil(tc.__lunit_setup) or is_function(tc.__lunit_setup)) |
|
516 orig_assert(is_nil(tc.__lunit_teardown) or is_function(tc.__lunit_teardown)) |
|
517 |
|
518 ---------------------------------- |
|
519 -- Protected call to a function -- |
|
520 ---------------------------------- |
|
521 |
|
522 local function call(errprefix, func) |
|
523 orig_assert(is_string(errprefix)) |
|
524 orig_assert(is_function(func)) |
|
525 local ok, errmsg = xpcall(function() func(tc) end, traceback) |
|
526 if not ok then |
|
527 print() |
|
528 print(errprefix..": "..errmsg) |
|
529 end |
|
530 return ok |
|
531 end |
|
532 |
|
533 ------------------------------------ |
|
534 -- Calls setup() on the Test Case -- |
|
535 ------------------------------------ |
|
536 |
|
537 local function setup(testname) |
|
538 if tc.__lunit_setup then |
|
539 return call("ERROR: "..testname..": setup() failed", tc.__lunit_setup) |
|
540 else |
|
541 return true |
|
542 end |
|
543 end |
|
544 |
|
545 ------------------------------------------ |
|
546 -- Calls a single Test on the Test Case -- |
|
547 ------------------------------------------ |
|
548 |
|
549 local function run(testname) |
|
550 orig_assert(is_string(testname)) |
|
551 orig_assert(is_function(tc[testname])) |
|
552 local ok = call("FAIL: "..testname, tc[testname]) |
|
553 if not ok then |
|
554 stats_inc("failed") |
|
555 else |
|
556 stats_inc("passed") |
|
557 end |
|
558 return ok |
|
559 end |
|
560 |
|
561 --------------------------------------- |
|
562 -- Calls teardown() on the Test Case -- |
|
563 --------------------------------------- |
|
564 |
|
565 local function teardown(testname) |
|
566 if tc.__lunit_teardown then |
|
567 if not call("WARNING: "..testname..": teardown() failed", tc.__lunit_teardown) then |
|
568 stats_inc("warnings") |
|
569 end |
|
570 end |
|
571 end |
|
572 |
|
573 --------------------------------- |
|
574 -- Run all Tests on a TestCase -- |
|
575 --------------------------------- |
|
576 |
|
577 print() |
|
578 print("#### Running '"..tc.__lunit_name.."' ("..table.getn(tc.__lunit_tests).." Tests)...") |
|
579 |
|
580 for _, testname in ipairs(tc.__lunit_tests) do |
|
581 if setup(testname) then |
|
582 run(testname) |
|
583 stats_inc("run") |
|
584 teardown(testname) |
|
585 else |
|
586 print("WARN: Skipping '"..testname.."'...") |
|
587 stats_inc("notrun") |
|
588 end |
|
589 end |
|
590 |
|
591 end |
|
592 |
|
593 |
|
594 |
|
595 |
|
596 --------------------- |
|
597 -- Import function -- |
|
598 --------------------- |
|
599 |
|
600 function import(name) |
|
601 |
|
602 do_assert(is_string(name), "lunit.import() expects a single string as argument") |
|
603 |
|
604 local user_env = getfenv(2) |
|
605 |
|
606 -------------------------------------------------- |
|
607 -- Installs a specific function in the user env -- |
|
608 -------------------------------------------------- |
|
609 |
|
610 local function install(funcname) |
|
611 user_env[funcname] = P[funcname] |
|
612 end |
|
613 |
|
614 |
|
615 ---------------------------------------------------------- |
|
616 -- Install functions matching a pattern in the user env -- |
|
617 ---------------------------------------------------------- |
|
618 |
|
619 local function install_pattern(pattern) |
|
620 for funcname, _ in pairs(P) do |
|
621 if string.find(funcname, pattern) then |
|
622 install(funcname) |
|
623 end |
|
624 end |
|
625 end |
|
626 |
|
627 ------------------------------------------------------------ |
|
628 -- Installs assert() and all assert_xxx() in the user env -- |
|
629 ------------------------------------------------------------ |
|
630 |
|
631 local function install_asserts() |
|
632 install_pattern("^assert.*") |
|
633 end |
|
634 |
|
635 ------------------------------------------- |
|
636 -- Installs all is_xxx() in the user env -- |
|
637 ------------------------------------------- |
|
638 |
|
639 local function install_tests() |
|
640 install_pattern("^is_.+") |
|
641 end |
|
642 |
|
643 if name == "asserts" or name == "assertions" then |
|
644 install_asserts() |
|
645 elseif name == "tests" or name == "checks" then |
|
646 install_tests() |
|
647 elseif name == "all" then |
|
648 install_asserts() |
|
649 install_tests() |
|
650 install("TestCase") |
|
651 elseif string.find(name, "^assert.*") and P[name] then |
|
652 install(name) |
|
653 elseif string.find(name, "^is_.+") and P[name] then |
|
654 install(name) |
|
655 elseif name == "TestCase" then |
|
656 install("TestCase") |
|
657 else |
|
658 error("luniit.import(): invalid function '"..name.."' to import", 2) |
|
659 end |
|
660 end |
|
661 |
|
662 |
|
663 |
|
664 |
|
665 -------------------------------------------------- |
|
666 -- Installs a private environment on the caller -- |
|
667 -------------------------------------------------- |
|
668 |
|
669 function setprivfenv() |
|
670 local new_env = { } |
|
671 local new_env_mt = { __index = getfenv(2) } |
|
672 setmetatable(new_env, new_env_mt) |
|
673 setfenv(2, new_env) |
|
674 end |
|
675 |
|
676 |
|
677 |
|
678 |
|
679 -------------------------------------------------- |
|
680 -- Increments a counter in the statistics table -- |
|
681 -------------------------------------------------- |
|
682 |
|
683 function stats_inc(varname, value) |
|
684 orig_assert(is_table(stats)) |
|
685 orig_assert(is_string(varname)) |
|
686 orig_assert(is_nil(value) or is_number(value)) |
|
687 if not stats[varname] then return end |
|
688 stats[varname] = stats[varname] + (value or 1) |
|
689 end |
|
690 |
|
691 |
|
692 |
|
693 |