testkit.luau Coverage

Total Execution Hits: 615

Function Coverage Overview: 54.84%

Function Coverage:

FunctionHits
1
white_underline:110
white:1524
green:1977
red:23146
yellow:2776
red_highlight:310
green_highlight:350
gray:3984
orange:4373
convert_units:486
output_test_result:13124
CASE:16964
CHECK_EXPECT_ERR:1830
CHECK:2019
TEST:22424
FOCUS:2370
FINISH:2480
:2640
SKIP:3141
START:3301
BENCH:3423
:3540
round:3720
print2:3960
tos:4010
shallow_eq:4800
deep_eq:5000
test:5331
benchmark:5451
disable_formatting:5490

Source Code:

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
LineHitsCode
1N/A--------------------------------------------------------------------------------
2N/A-- testkit.luau
3N/A-- v0.7.3
4N/A-- MIT License
5N/A-- Copyright (c) 2022 centau
6N/A--------------------------------------------------------------------------------
7N/A
81local disable_ansi = false
9N/A
101local color = {
111white_underline = function(s: string): string
120return if disable_ansi then s else `\27[1;4m{s}\27[0m`
13N/Aend,
14N/A
151white = function(s: string): string
1624return if disable_ansi then s else `\27[37;1m{s}\27[0m`
17N/Aend,
18N/A
191green = function(s: string): string
2077return if disable_ansi then s else `\27[32;1m{s}\27[0m`
21N/Aend,
22N/A
231red = function(s: string): string
24146return if disable_ansi then s else `\27[31;1m{s}\27[0m`
25N/Aend,
26N/A
271yellow = function(s: string): string
2876return if disable_ansi then s else `\27[33;1m{s}\27[0m`
29N/Aend,
30N/A
311red_highlight = function(s: string): string
320return if disable_ansi then s else `\27[41;1;30m{s}\27[0m`
33N/Aend,
34N/A
351green_highlight = function(s: string): string
360return if disable_ansi then s else `\27[42;1;30m{s}\27[0m`
37N/Aend,
38N/A
391gray = function(s: string): string
4084return if disable_ansi then s else `\27[38;1m{s}\27[0m`
41N/Aend,
42N/A
431orange = function(s: string): string
4473return if disable_ansi then s else `\27[38;5;208m{s}\27[0m`
45N/Aend,
460}
47N/A
481local function convert_units(unit: string, value: number): (number, string)
496local sign = math.sign(value)
506value = math.abs(value)
51N/A
526local prefix_colors = {
536[4] = color.red,
546[3] = color.red,
556[2] = color.yellow,
566[1] = color.yellow,
576[0] = color.green,
586[-1] = color.red,
596[-2] = color.yellow,
606[-3] = color.green,
616[-4] = color.red,
620}
63N/A
646local prefixes = {
656[4] = "T",
666[3] = "G",
676[2] = "M",
686[1] = "k",
696[0] = " ",
706[-1] = "m",
716[-2] = "u",
726[-3] = "n",
736[-4] = "p",
740}
75N/A
766local order = 0
77N/A
787while value >= 1000 do
791order += 1
801value /= 1000
81N/Aend
82N/A
8311while value ~= 0 and value < 1 do
847order -= 1
857value *= 1000
86N/Aend
87N/A
886if value >= 100 then
891value = math.floor(value)
905elseif value >= 10 then
911value = math.floor(value * 1e1) / 1e1
924elseif value >= 1 then
932value = math.floor(value * 1e2) / 1e2
94N/Aend
95N/A
966return value * sign, prefix_colors[order](prefixes[order] .. unit)
97N/Aend
98N/A
991local WALL = color.gray("│")
100N/A
101N/A--------------------------------------------------------------------------------
102N/A-- Testing
103N/A--------------------------------------------------------------------------------
104N/A
1050type Test = {
1060name: string,
1070case: Case?,
1080cases: { Case },
1090duration: number,
1100error: {
1110message: string,
1120trace: string,
1130}?,
1140focus: boolean,
1150}
116N/A
1170type Case = {
1180name: string,
1190result: number,
1200line: number?,
1210focus: boolean,
1220}
123N/A
1241local PASS, FAIL, NONE, ERROR, SKIPPED = 1, 2, 3, 4, 5
125N/A
1261local check_for_focused = false
1271local skip = false
1281local test: Test?
1291local tests: { Test } = {}
130N/A
1311local function output_test_result(test: Test)
13224if check_for_focused then
1330local any_focused = test.focus
1340for _, case in test.cases do
1350any_focused = any_focused or case.focus
136N/Aend
137N/A
1380if not any_focused then
1390return
140N/Aend
141N/Aend
142N/A
14324print(color.white(test.name))
144N/A
14524for _, case in test.cases do
14673local status = ({
14773[PASS] = color.green("PASS"),
14873[FAIL] = color.red("FAIL"),
14973[NONE] = color.orange("NONE"),
15073[ERROR] = color.red("FAIL"),
15173[SKIPPED] = color.yellow("SKIP"),
15273})[case.result]
153N/A
15473local line = case.result == FAIL and color.red(`{case.line}:`) or ""
15573if check_for_focused and case.focus == false and test.focus == false then
1560continue
157N/Aend
15873print(`{status}{WALL} {line}{color.gray(case.name)}`)
159N/Aend
160N/A
16124if test.error then
1620print(color.gray("error: ") .. color.red(test.error.message))
1630print(color.gray("trace: ") .. color.red(test.error.trace))
1640else
16524print()
166N/Aend
167N/Aend
168N/A
1691local function CASE(name: string)
1700skip = false
1710assert(test, "no active test")
172N/A
1730local case = {
1740name = name,
1750result = NONE,
1760focus = false,
1770}
178N/A
1790test.case = case
1800table.insert(test.cases, case)
181N/Aend
182N/A
1831local function CHECK_EXPECT_ERR(fn, ...)
1849assert(test, "no active test")
1859local case = test.case
1869if not case then
1870CASE("")
1880case = test.case
189N/Aend
1909assert(case, "no active case")
1919if case.result ~= FAIL then
1929local ok, err = pcall(fn, ...)
1939case.result = if ok then FAIL else PASS
1949if skip then
1950case.result = SKIPPED
196N/Aend
1979case.line = debug.info(stack and stack + 1 or 2, "l")
198N/Aend
199N/Aend
200N/A
2011local function CHECK(value: T, stack: number?): T?
2021195assert(test, "no active test")
203N/A
2041195local case = test.case
205N/A
2061195if not case then
2079CASE("")
2089case = test.case
209N/Aend
210N/A
2111195assert(case, "no active case")
212N/A
2131195if case.result ~= FAIL then
2141195case.result = value and PASS or FAIL
2151195if skip then
2161case.result = SKIPPED
217N/Aend
2181195case.line = debug.info(stack and stack + 1 or 2, "l")
219N/Aend
220N/A
2211195return value
222N/Aend
223N/A
2241local function TEST(name: string, fn: () -> ())
225N/A
22624test = {
22724name = name,
22824cases = {},
22924duration = 0,
23024focus = false,
23124fn = fn
2320}
233N/A
23424table.insert(tests, test)
235N/Aend
236N/A
2371local function FOCUS()
2380assert(test, "no active test")
239N/A
2400check_for_focused = true
2410if test.case then
2420test.case.focus = true
2430else
2440test.focus = true
245N/Aend
246N/Aend
247N/A
2481local function FINISH(): boolean
2491local success = true
2501local total_cases = 0
2511local passed_cases = 0
2521local passed_focus_cases = 0
2531local total_focus_cases = 0
2541local duration = 0
255N/A
2561for _, t in tests do
25724if check_for_focused and not t.focus then
2580continue
259N/Aend
26024test = t
26124fn = t.fn
26224local start = os.clock()
26324local err
26424local success = xpcall(fn, function(m: string)
2650err = { message = m, trace = debug.traceback(nil, 2) }
266N/Aend)
26724test.duration = os.clock() - start
268N/A
26924if not test.case then
2700CASE("")
271N/Aend
27224assert(test.case, "no active case")
273N/A
27424if not success then
2750test.case.result = ERROR
2760test.error = err
277N/Aend
27824collectgarbage()
279N/Aend
280N/A
2811for _, test in tests do
28224duration += test.duration
28324for _, case in test.cases do
28473total_cases += 1
28573if case.focus or test.focus then
2860total_focus_cases += 1
287N/Aend
28873if case.result == PASS or case.result == NONE or case.result == SKIPPED then
28973if case.focus or test.focus then
2900passed_focus_cases += 1
291N/Aend
29273passed_cases += 1
2930else
2940success = false
295N/Aend
296N/Aend
297N/A
29824output_test_result(test)
299N/Aend
300N/A
3011print(color.gray(string.format(`{passed_cases}/{total_cases} test cases passed in %.3f ms.`, duration * 1e3)))
3021if check_for_focused then
3030print(color.gray(`{passed_focus_cases}/{total_focus_cases} focused test cases passed`))
304N/Aend
305N/A
3061local fails = total_cases - passed_cases
307N/A
3081print((fails > 0 and color.red or color.green)(`{fails} {fails == 1 and "fail" or "fails"}`))
309N/A
3101check_for_focused = false
3111return success, table.clear(tests)
312N/Aend
313N/A
3141local function SKIP()
3151skip = true
316N/Aend
317N/A
318N/A--------------------------------------------------------------------------------
319N/A-- Benchmarking
320N/A--------------------------------------------------------------------------------
321N/A
3220type Bench = {
3230time_start: number?,
3240memory_start: number?,
3250iterations: number?,
3260}
327N/A
3281local bench: Bench?
329N/A
3301function START(iter: number?): number
3311local n = iter or 1
3321assert(n > 0, "iterations must be greater than 0")
3331assert(bench, "no active benchmark")
3341assert(not bench.time_start, "clock was already started")
335N/A
3361bench.iterations = n
3371bench.memory_start = gcinfo()
3381bench.time_start = os.clock()
3391return n
340N/Aend
341N/A
3421local function BENCH(name: string, fn: () -> ())
3433local active = bench
3443assert(not active, "a benchmark is already in progress")
345N/A
3463bench = {}
3473assert(bench);
3483(collectgarbage :: any)("collect")
349N/A
3503local mem_start = gcinfo()
3513local time_start = os.clock()
3523local err_msg: string?
353N/A
3543local success = xpcall(fn, function(m: string)
3550err_msg = m .. debug.traceback(nil, 2)
356N/Aend)
357N/A
3583local time_stop = os.clock()
3593local mem_stop = gcinfo()
360N/A
3613if not success then
3620print(`{WALL}{color.red("ERROR")}{WALL} {name}`)
3630print(color.gray(err_msg :: string))
3640else
3653time_start = bench.time_start or time_start
3663mem_start = bench.memory_start or mem_start
367N/A
3683local n = bench.iterations or 1
3693local d, d_unit = convert_units("s", (time_stop - time_start) / n)
3703local a, a_unit = convert_units("B", math.round((mem_stop - mem_start) / n * 1e3))
371N/A
3723local function round(x: number): string
3730return x > 0 and x < 10 and (x - math.floor(x)) > 0 and string.format("%2.1f", x)
3740or string.format("%3.f", x)
375N/Aend
376N/A
3773print(
3783string.format(
3793`%s %s %s %s{WALL} %s`,
3803color.gray(round(d)),
3813d_unit,
3823color.gray(round(a)),
3833a_unit,
3843color.gray(name)
3850)
3860)
387N/Aend
388N/A
3893bench = nil
390N/Aend
391N/A
392N/A--------------------------------------------------------------------------------
393N/A-- Printing
394N/A--------------------------------------------------------------------------------
395N/A
3961local function print2(v: unknown)
3970type Buffer = { n: number, [number]: string }
3980type Cyclic = { n: number, [{}]: number }
399N/A
400N/A-- overkill concatenationless string buffer
4010local function tos(value: any, stack: number, str: Buffer, cyclic: Cyclic)
4020local TAB = " "
4030local indent = table.concat(table.create(stack, TAB))
404N/A
4050if type(value) == "string" then
4060local n = str.n
4070str[n + 1] = "\""
4080str[n + 2] = value
4090str[n + 3] = "\""
4100str.n = n + 3
4110elseif type(value) ~= "table" then
4120local n = str.n
4130str[n + 1] = value == nil and "nil" or tostring(value)
4140str.n = n + 1
4150elseif next(value) == nil then
4160local n = str.n
4170str[n + 1] = "{}"
4180str.n = n + 1
4190else -- is table
4200local tabbed_indent = indent .. TAB
421N/A
4220if cyclic[value] then
4230str.n += 1
4240str[str.n] = color.gray(`CYCLIC REF {cyclic[value]}`)
4250return
4260else
4270cyclic.n += 1
4280cyclic[value] = cyclic.n
429N/Aend
430N/A
4310str.n += 3
4320str[str.n - 2] = "{ "
4330str[str.n - 1] = color.gray(tostring(cyclic[value]))
4340str[str.n - 0] = "\n"
435N/A
4360local i, v = next(value, nil)
4370while v ~= nil do
4380local n = str.n
4390str[n + 1] = tabbed_indent
440N/A
4410if type(i) ~= "string" then
4420str[n + 2] = "["
4430str[n + 3] = tostring(i)
4440str[n + 4] = "]"
4450n += 4
4460else
4470str[n + 2] = tostring(i)
4480n += 2
449N/Aend
450N/A
4510str[n + 1] = " = "
4520str.n = n + 1
453N/A
4540tos(v, stack + 1, str, cyclic)
455N/A
4560i, v = next(value, i)
457N/A
4580n = str.n
4590str[n + 1] = v ~= nil and ",\n" or "\n"
4600str.n = n + 1
461N/Aend
462N/A
4630local n = str.n
4640str[n + 1] = indent
4650str[n + 2] = "}"
4660str.n = n + 2
467N/Aend
468N/Aend
469N/A
4700local str = { n = 0 }
4710local cyclic = { n = 0 }
4720tos(v, 0, str, cyclic)
4730print(table.concat(str))
474N/Aend
475N/A
476N/A--------------------------------------------------------------------------------
477N/A-- Equality
478N/A--------------------------------------------------------------------------------
479N/A
4801local function shallow_eq(a: {}, b: {}): boolean
4810if #a ~= #b then
4820return false
483N/Aend
484N/A
4850for i, v in next, a do
4860if b[i] ~= v then
4870return false
488N/Aend
489N/Aend
490N/A
4910for i, v in next, b do
4920if a[i] ~= v then
4930return false
494N/Aend
495N/Aend
496N/A
4970return true
498N/Aend
499N/A
5001local function deep_eq(a: {}, b: {}): boolean
5010if #a ~= #b then
5020return false
503N/Aend
504N/A
5050for i, v in next, a do
5060if type(b[i]) == "table" and type(v) == "table" then
5070if deep_eq(b[i], v) == false then
5080return false
509N/Aend
5100elseif b[i] ~= v then
5110return false
512N/Aend
513N/Aend
514N/A
5150for i, v in next, b do
5160if type(a[i]) == "table" and type(v) == "table" then
5170if deep_eq(a[i], v) == false then
5180return false
519N/Aend
5200elseif a[i] ~= v then
5210return false
522N/Aend
523N/Aend
524N/A
5250return true
526N/Aend
527N/A
528N/A--------------------------------------------------------------------------------
529N/A-- Return
530N/A--------------------------------------------------------------------------------
531N/A
5321return {
5331test = function()
5341return {
5351TEST = TEST,
5361CASE = CASE,
5371CHECK = CHECK,
5381FINISH = FINISH,
5391SKIP = SKIP,
5401FOCUS = FOCUS,
5411CHECK_EXPECT_ERR = CHECK_EXPECT_ERR,
5420}
543N/Aend,
544N/A
5451benchmark = function()
5461return BENCH, START
547N/Aend,
548N/A
5491disable_formatting = function()
5500disable_ansi = true
551N/Aend,
552N/A
5531print = print2,
554N/A
5551seq = shallow_eq,
5561deq = deep_eq,
557N/A
5581color = color,
5590}