├── spec ├── filefilter │ ├── test2.lua │ ├── test3.lua │ ├── .luacov │ ├── test.lua │ ├── 2.luacov │ ├── expected2.out │ └── expected.out ├── LUACOV_CONFIG │ ├── test1.lua │ ├── test2.lua │ ├── luacov.config.lua │ ├── test.lua │ └── expected.out ├── dirfilter │ ├── dirA │ │ └── fileA.lua │ ├── dirB │ │ └── fileB.lua │ ├── dirC │ │ ├── fileC.lua │ │ └── nested │ │ │ └── fileD.lua │ ├── 3.luacov │ ├── .luacov │ ├── 2.luacov │ ├── 4.luacov │ ├── test.lua │ ├── expected.out │ ├── expected4.out │ ├── expected2.out │ └── expected3.out ├── includeuntestedfiles │ ├── example-src │ │ ├── third-module │ │ │ ├── lib-1.lua │ │ │ └── lib-2.lua │ │ ├── moduleA │ │ │ ├── libA.lua │ │ │ └── libB.lua │ │ └── moduleB │ │ │ ├── aLib.lua │ │ │ └── bLib.lua │ ├── .luacov │ ├── subdir │ │ ├── .luacov │ │ ├── test.lua │ │ └── expected.out │ ├── 2.luacov │ ├── 3.luacov │ ├── test.lua │ ├── expected3.out │ ├── expected2.out │ └── expected.out ├── cluacov │ ├── test.lua │ └── expected.out ├── nested │ ├── .luacov │ ├── subdir │ │ ├── .luacov │ │ ├── script.lua │ │ └── tick.luacov │ ├── testlib.lua │ ├── expected.out │ └── test.lua ├── hook │ ├── my_hook.lua │ ├── test.lua │ └── expected.out ├── simple │ ├── test.lua │ └── expected.out ├── shebang │ ├── test.lua │ └── expected.out ├── coroutines │ ├── test.lua │ └── expected.out ├── filefilter_spec.lua ├── cli_spec.lua └── linescanner_spec.lua ├── src ├── luacov │ ├── reporter │ │ ├── default.lua │ │ ├── html │ │ │ ├── static │ │ │ │ ├── lang-lua.js │ │ │ │ ├── report.js │ │ │ │ ├── report.css │ │ │ │ └── prettify.js │ │ │ └── template.lua │ │ ├── lcov.lua │ │ └── html.lua │ ├── tick.lua │ ├── stats.lua │ ├── hook.lua │ ├── defaults.lua │ ├── util.lua │ └── linescanner.lua ├── luacov.lua ├── fileutil.c └── bin │ └── luacov ├── docs ├── luacov.png ├── logo │ ├── luacov.png │ ├── luacov-120x120.png │ ├── luacov-144x144.png │ ├── luacov-skynet.png │ └── luacov.svg ├── luacov-html-reporter.png ├── doc │ ├── modules │ │ ├── luacov.tick.html │ │ ├── luacov.html │ │ ├── luacov.hook.html │ │ ├── luacov.stats.html │ │ ├── luacov.util.html │ │ ├── luacov.defaults.html │ │ └── luacov.reporter.html │ ├── index.html │ └── ldoc.css ├── README-zh.md ├── license.html ├── doc.css └── index.html ├── .busted ├── config.ld ├── .editorconfig ├── .luacheckrc ├── .gitignore ├── LICENSE ├── appveyor.yml ├── .github └── workflows │ ├── lint.yml │ └── unix_build.yml ├── luacov-scm-1.rockspec └── README.md /spec/filefilter/test2.lua: -------------------------------------------------------------------------------- 1 | local a = 1 2 | -------------------------------------------------------------------------------- /spec/filefilter/test3.lua: -------------------------------------------------------------------------------- 1 | local b = 2 2 | -------------------------------------------------------------------------------- /spec/LUACOV_CONFIG/test1.lua: -------------------------------------------------------------------------------- 1 | local a = 1 2 | -------------------------------------------------------------------------------- /spec/LUACOV_CONFIG/test2.lua: -------------------------------------------------------------------------------- 1 | local b = 2 2 | -------------------------------------------------------------------------------- /spec/dirfilter/dirA/fileA.lua: -------------------------------------------------------------------------------- 1 | return "This is file A" 2 | -------------------------------------------------------------------------------- /spec/filefilter/.luacov: -------------------------------------------------------------------------------- 1 | include = {"test$", "test2"} 2 | -------------------------------------------------------------------------------- /spec/LUACOV_CONFIG/luacov.config.lua: -------------------------------------------------------------------------------- 1 | include = { "test2" } 2 | -------------------------------------------------------------------------------- /spec/dirfilter/dirB/fileB.lua: -------------------------------------------------------------------------------- 1 | return "This is another file" 2 | -------------------------------------------------------------------------------- /src/luacov/reporter/default.lua: -------------------------------------------------------------------------------- 1 | return require "luacov.reporter" -------------------------------------------------------------------------------- /spec/LUACOV_CONFIG/test.lua: -------------------------------------------------------------------------------- 1 | require "test1" 2 | require "test2" 3 | -------------------------------------------------------------------------------- /spec/dirfilter/dirC/fileC.lua: -------------------------------------------------------------------------------- 1 | return "This is the final file" 2 | -------------------------------------------------------------------------------- /spec/filefilter/test.lua: -------------------------------------------------------------------------------- 1 | require "test2" 2 | require "test3" 3 | -------------------------------------------------------------------------------- /spec/includeuntestedfiles/example-src/third-module/lib-1.lua: -------------------------------------------------------------------------------- 1 | return 5 2 | -------------------------------------------------------------------------------- /spec/includeuntestedfiles/example-src/third-module/lib-2.lua: -------------------------------------------------------------------------------- 1 | return 0 2 | -------------------------------------------------------------------------------- /spec/includeuntestedfiles/example-src/moduleA/libA.lua: -------------------------------------------------------------------------------- 1 | local a = 1 + 1 2 | -------------------------------------------------------------------------------- /spec/includeuntestedfiles/example-src/moduleB/aLib.lua: -------------------------------------------------------------------------------- 1 | local val = "ue" 2 | -------------------------------------------------------------------------------- /spec/includeuntestedfiles/example-src/moduleB/bLib.lua: -------------------------------------------------------------------------------- 1 | local test = 123 2 | -------------------------------------------------------------------------------- /spec/cluacov/test.lua: -------------------------------------------------------------------------------- 1 | local s = "some text" 2 | .. "some other" 3 | return s 4 | -------------------------------------------------------------------------------- /spec/dirfilter/dirC/nested/fileD.lua: -------------------------------------------------------------------------------- 1 | return "This is the secret nested file" 2 | -------------------------------------------------------------------------------- /spec/includeuntestedfiles/example-src/moduleA/libB.lua: -------------------------------------------------------------------------------- 1 | local b = 1 - math.huge 2 | -------------------------------------------------------------------------------- /docs/luacov.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rakuv3r/luacov-skynet/HEAD/docs/luacov.png -------------------------------------------------------------------------------- /docs/logo/luacov.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rakuv3r/luacov-skynet/HEAD/docs/logo/luacov.png -------------------------------------------------------------------------------- /.busted: -------------------------------------------------------------------------------- 1 | return { 2 | default = { 3 | verbose = true, 4 | output = "gtest", 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /spec/nested/.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | modules = { 3 | testlib = "testlib.lua" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /spec/dirfilter/3.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | -- No custom include patterns, should include all files 3 | } 4 | -------------------------------------------------------------------------------- /docs/logo/luacov-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rakuv3r/luacov-skynet/HEAD/docs/logo/luacov-120x120.png -------------------------------------------------------------------------------- /docs/logo/luacov-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rakuv3r/luacov-skynet/HEAD/docs/logo/luacov-144x144.png -------------------------------------------------------------------------------- /docs/logo/luacov-skynet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rakuv3r/luacov-skynet/HEAD/docs/logo/luacov-skynet.png -------------------------------------------------------------------------------- /spec/nested/subdir/.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | savestepsize = 1, 3 | statsfile = "../luacov.stats.out" 4 | } 5 | -------------------------------------------------------------------------------- /docs/luacov-html-reporter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rakuv3r/luacov-skynet/HEAD/docs/luacov-html-reporter.png -------------------------------------------------------------------------------- /spec/filefilter/2.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | include = {"test$", "test2$"}, 3 | exclude = {"test$", "test3$"} 4 | } 5 | -------------------------------------------------------------------------------- /spec/hook/my_hook.lua: -------------------------------------------------------------------------------- 1 | local runner = require "luacov.runner" 2 | return function(_, line) runner.debug_hook(_, line, 3) end 3 | -------------------------------------------------------------------------------- /spec/includeuntestedfiles/.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | -- All files implicitly included 3 | includeuntestedfiles = true 4 | } 5 | -------------------------------------------------------------------------------- /spec/nested/subdir/script.lua: -------------------------------------------------------------------------------- 1 | local testlib = require "testlib" 2 | testlib.f1() 3 | testlib.f2() 4 | testlib.f2() 5 | osexit() 6 | -------------------------------------------------------------------------------- /spec/nested/subdir/tick.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | savestepsize = 1, 3 | statsfile = "../luacov.stats.out", 4 | tick = true 5 | } 6 | -------------------------------------------------------------------------------- /spec/simple/test.lua: -------------------------------------------------------------------------------- 1 | if 10 > 100 then 2 | local msg = "I don't think this line will execute." 3 | else 4 | local msg = "Hello, LuaCov!" 5 | end 6 | -------------------------------------------------------------------------------- /spec/dirfilter/.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | -- Include a single directory that has no nested directories 3 | include = { 4 | "dirA%/*" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /spec/dirfilter/2.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | -- Include a single directory that has a nested directory 3 | include = { 4 | "dirC%/*" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /spec/dirfilter/4.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | -- Filter via modules config option 3 | modules = { 4 | ["dirA.*"] = ".", 5 | ["dirB.*"] = "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /spec/shebang/test.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | if 10 > 100 then 3 | local msg = "I don't think this line will execute." 4 | else 5 | local msg = "Hello, LuaCov!" 6 | end 7 | -------------------------------------------------------------------------------- /config.ld: -------------------------------------------------------------------------------- 1 | project = "LuaCov" 2 | description = "Coverage analysis tool for Lua scripts" 3 | title = "LuaCov Reference" 4 | dir = "docs/doc" 5 | file = "src" 6 | format = "markdown" 7 | -------------------------------------------------------------------------------- /spec/includeuntestedfiles/subdir/.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | include = { 3 | "..%/example%-src/*" 4 | }, 5 | includeuntestedfiles = { 6 | "../example-src" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /spec/includeuntestedfiles/2.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | include = { 3 | "example%-src%/moduleA%/*" 4 | }, 5 | 6 | includeuntestedfiles = { 7 | "example-src/moduleA" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /spec/hook/test.lua: -------------------------------------------------------------------------------- 1 | local runner = require "luacov.runner" 2 | local my_hook = require "my_hook" 3 | debug.sethook(my_hook, "line") 4 | local a = 2 5 | debug.sethook(runner.debug_hook, "line") 6 | local b = 3 7 | -------------------------------------------------------------------------------- /spec/includeuntestedfiles/3.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | modules = { 3 | ["libB"] = "example-src/moduleA/libB.lua" 4 | }, 5 | includeuntestedfiles = { 6 | "example-src/moduleA/libB.lua" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /spec/includeuntestedfiles/test.lua: -------------------------------------------------------------------------------- 1 | 2 | -- Don't load some of the modules to prevent them from getting tested/executed 3 | require "example-src.moduleA.libA" 4 | require "example-src.third-module.lib-1" 5 | require "example-src.third-module.lib-2" 6 | -------------------------------------------------------------------------------- /spec/nested/testlib.lua: -------------------------------------------------------------------------------- 1 | local a = 1 2 | local b = 2 3 | 4 | local lib = {} 5 | 6 | function lib.f1() 7 | local c = 3 8 | return 4 9 | end 10 | 11 | function lib.f2() 12 | local d = 5 13 | return 6 14 | end 15 | 16 | return lib 17 | -------------------------------------------------------------------------------- /spec/includeuntestedfiles/subdir/test.lua: -------------------------------------------------------------------------------- 1 | 2 | package.path = package.path .. ";../?.lua" 3 | 4 | -- Don't load some of the modules to prevent them from getting tested/executed 5 | require "example-src.moduleA.libA" 6 | require "example-src.third-module.lib-1" 7 | require "example-src.third-module.lib-2" 8 | -------------------------------------------------------------------------------- /src/luacov.lua: -------------------------------------------------------------------------------- 1 | --- Loads `luacov.runner` and immediately starts it. 2 | -- Useful for launching scripts from the command-line. Returns the `luacov.runner` module. 3 | -- @class module 4 | -- @name luacov 5 | -- @usage lua -lluacov sometest.lua 6 | local runner = require("luacov.runner") 7 | runner.init() 8 | return runner 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | charset = utf-8 8 | 9 | [*.lua] 10 | indent_style = space 11 | indent_size = 3 12 | 13 | [*.rockspec] 14 | indent_style = space 15 | indent_size = 3 16 | 17 | [*.md] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | [Makefile] 22 | indent_style = tab 23 | indent_size = 4 24 | -------------------------------------------------------------------------------- /spec/coroutines/test.lua: -------------------------------------------------------------------------------- 1 | local runner = require "luacov.runner" 2 | 3 | local function f(x) 4 | return coroutine.yield(x + 1) + 2 5 | end 6 | 7 | local function g(x) 8 | return coroutine.yield(x + 3) + 4 9 | end 10 | 11 | local wf = coroutine.wrap(f) 12 | local wg = corowrap(runner.with_luacov(g)) 13 | 14 | assert(wf(3) == 4) 15 | assert(wf(5) == 7) 16 | assert(wg(8) == 11) 17 | assert(wg(10) == 14) 18 | -------------------------------------------------------------------------------- /spec/dirfilter/test.lua: -------------------------------------------------------------------------------- 1 | 2 | -- This script expects the arguments "-c " 3 | 4 | -- "Unload" the luacov.runner module which was included via "-lluacov" to be able to load a specific config file 5 | package.loaded["luacov.runner"] = nil 6 | 7 | -- Initialize the luacov.runner module with the luacov config file for the current test case 8 | require("luacov.runner")(arg[2]) 9 | 10 | require "dirA.fileA" 11 | require "dirB.fileB" 12 | require "dirC.fileC" 13 | require "dirC.nested.fileD" 14 | -------------------------------------------------------------------------------- /src/fileutil.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | static int file_exists(lua_State *L) { 8 | const char *filename = luaL_checkstring(L, 1); 9 | 10 | int exists = access(filename, F_OK) != -1; 11 | 12 | lua_pushboolean(L, exists); 13 | 14 | return 1; 15 | } 16 | 17 | static const luaL_Reg mylib[] = { 18 | {"file_exists", file_exists}, 19 | {NULL, NULL} 20 | }; 21 | 22 | int luaopen_fileutil(lua_State *L) { 23 | luaL_newlib(L, mylib); 24 | return 1; 25 | } -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | std = "min" 2 | 3 | not_globals = { 4 | -- deprecated Lua 5.0 functions 5 | "string.len", 6 | "table.getn", 7 | } 8 | 9 | include_files = { 10 | "**/*.lua", 11 | "**/*.rockspec", 12 | ".busted", 13 | ".luacheckrc", 14 | } 15 | 16 | exclude_files = { 17 | "spec/*/*", 18 | "src/luacov/reporter/html/static/*.js", 19 | "src/luacov/reporter/html/static/*.css", 20 | 21 | -- The Github Actions Lua Environment 22 | ".lua", 23 | ".luarocks", 24 | ".install", 25 | } 26 | 27 | files["spec/**/*.lua"] = { 28 | std = "+busted", 29 | } 30 | -------------------------------------------------------------------------------- /spec/LUACOV_CONFIG/expected.out: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | test2.lua 3 | ============================================================================== 4 | 1 local b = 2 5 | 6 | ============================================================================== 7 | Summary 8 | ============================================================================== 9 | 10 | File Hits Missed Coverage 11 | ------------------------------ 12 | test2.lua 1 0 100.00% 13 | ------------------------------ 14 | Total 1 0 100.00% 15 | -------------------------------------------------------------------------------- /spec/filefilter/expected2.out: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | test2.lua 3 | ============================================================================== 4 | 1 local a = 1 5 | 6 | ============================================================================== 7 | Summary 8 | ============================================================================== 9 | 10 | File Hits Missed Coverage 11 | ------------------------------ 12 | test2.lua 1 0 100.00% 13 | ------------------------------ 14 | Total 1 0 100.00% 15 | -------------------------------------------------------------------------------- /spec/cluacov/expected.out: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | test.lua 3 | ============================================================================== 4 | local s = "some text" 5 | 1 .. "some other" 6 | 1 return s 7 | 8 | ============================================================================== 9 | Summary 10 | ============================================================================== 11 | 12 | File Hits Missed Coverage 13 | ----------------------------- 14 | test.lua 2 0 100.00% 15 | ----------------------------- 16 | Total 2 0 100.00% 17 | -------------------------------------------------------------------------------- /spec/dirfilter/expected.out: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | dirA/fileA.lua 3 | ============================================================================== 4 | 1 return "This is file A" 5 | 6 | ============================================================================== 7 | Summary 8 | ============================================================================== 9 | 10 | File Hits Missed Coverage 11 | ----------------------------------- 12 | dirA/fileA.lua 1 0 100.00% 13 | ----------------------------------- 14 | Total 1 0 100.00% 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Lua sources 2 | luac.out 3 | 4 | # LuaCov files 5 | *.report.out 6 | *.stats.out 7 | 8 | # luarocks build files 9 | *.rock 10 | *.zip 11 | *.tar.gz 12 | 13 | # Object files 14 | *.o 15 | *.os 16 | *.ko 17 | *.obj 18 | *.elf 19 | 20 | # Precompiled Headers 21 | *.gch 22 | *.pch 23 | 24 | # Libraries 25 | *.lib 26 | *.a 27 | *.la 28 | *.lo 29 | *.def 30 | *.exp 31 | 32 | # Shared objects (inc. Windows DLLs) 33 | *.dll 34 | *.so 35 | *.so.* 36 | *.dylib 37 | 38 | # Executables 39 | *.exe 40 | *.out 41 | *.app 42 | *.i*86 43 | *.x86_64 44 | *.hex 45 | 46 | .idea 47 | 48 | /luarocks 49 | /lua 50 | /lua_modules 51 | /.luarocks 52 | -------------------------------------------------------------------------------- /spec/simple/expected.out: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | test.lua 3 | ============================================================================== 4 | 1 if 10 > 100 then 5 | *0 local msg = "I don't think this line will execute." 6 | else 7 | 1 local msg = "Hello, LuaCov!" 8 | end 9 | 10 | ============================================================================== 11 | Summary 12 | ============================================================================== 13 | 14 | File Hits Missed Coverage 15 | ----------------------------- 16 | test.lua 2 1 66.67% 17 | ----------------------------- 18 | Total 2 1 66.67% 19 | -------------------------------------------------------------------------------- /spec/includeuntestedfiles/expected3.out: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | example-src/moduleA/libB.lua 3 | ============================================================================== 4 | *0 local b = 1 - math.huge 5 | 6 | ============================================================================== 7 | Summary 8 | ============================================================================== 9 | 10 | File Hits Missed Coverage 11 | ------------------------------------------------- 12 | example-src/moduleA/libB.lua 0 1 0.00% 13 | ------------------------------------------------- 14 | Total 0 1 0.00% 15 | -------------------------------------------------------------------------------- /spec/shebang/expected.out: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | test.lua 3 | ============================================================================== 4 | #!/usr/bin/env lua 5 | 1 if 10 > 100 then 6 | *0 local msg = "I don't think this line will execute." 7 | else 8 | 1 local msg = "Hello, LuaCov!" 9 | end 10 | 11 | ============================================================================== 12 | Summary 13 | ============================================================================== 14 | 15 | File Hits Missed Coverage 16 | ----------------------------- 17 | test.lua 2 1 66.67% 18 | ----------------------------- 19 | Total 2 1 66.67% 20 | -------------------------------------------------------------------------------- /spec/nested/expected.out: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | testlib.lua 3 | ============================================================================== 4 | 3 local a = 1 5 | 3 local b = 2 6 | 7 | 3 local lib = {} 8 | 9 | 3 function lib.f1() 10 | 3 local c = 3 11 | 3 return 4 12 | end 13 | 14 | 3 function lib.f2() 15 | 5 local d = 5 16 | 5 return 6 17 | end 18 | 19 | 3 return lib 20 | 21 | ============================================================================== 22 | Summary 23 | ============================================================================== 24 | 25 | File Hits Missed Coverage 26 | -------------------------------- 27 | testlib.lua 10 0 100.00% 28 | -------------------------------- 29 | Total 10 0 100.00% 30 | -------------------------------------------------------------------------------- /spec/filefilter/expected.out: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | test.lua 3 | ============================================================================== 4 | 1 require "test2" 5 | 1 require "test3" 6 | 7 | ============================================================================== 8 | test2.lua 9 | ============================================================================== 10 | 1 local a = 1 11 | 12 | ============================================================================== 13 | Summary 14 | ============================================================================== 15 | 16 | File Hits Missed Coverage 17 | ------------------------------ 18 | test.lua 2 0 100.00% 19 | test2.lua 1 0 100.00% 20 | ------------------------------ 21 | Total 3 0 100.00% 22 | -------------------------------------------------------------------------------- /spec/dirfilter/expected4.out: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | dirA/fileA.lua 3 | ============================================================================== 4 | 1 return "This is file A" 5 | 6 | ============================================================================== 7 | dirB/fileB.lua 8 | ============================================================================== 9 | 1 return "This is another file" 10 | 11 | ============================================================================== 12 | Summary 13 | ============================================================================== 14 | 15 | File Hits Missed Coverage 16 | ----------------------------------- 17 | dirA/fileA.lua 1 0 100.00% 18 | dirB/fileB.lua 1 0 100.00% 19 | ----------------------------------- 20 | Total 2 0 100.00% 21 | -------------------------------------------------------------------------------- /spec/dirfilter/expected2.out: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | dirC/fileC.lua 3 | ============================================================================== 4 | 1 return "This is the final file" 5 | 6 | ============================================================================== 7 | dirC/nested/fileD.lua 8 | ============================================================================== 9 | 1 return "This is the secret nested file" 10 | 11 | ============================================================================== 12 | Summary 13 | ============================================================================== 14 | 15 | File Hits Missed Coverage 16 | ------------------------------------------ 17 | dirC/fileC.lua 1 0 100.00% 18 | dirC/nested/fileD.lua 1 0 100.00% 19 | ------------------------------------------ 20 | Total 2 0 100.00% 21 | -------------------------------------------------------------------------------- /spec/coroutines/expected.out: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | test.lua 3 | ============================================================================== 4 | 1 local runner = require "luacov.runner" 5 | 6 | local function f(x) 7 | X return coroutine.yield(x + 1) + 2 8 | end 9 | 10 | local function g(x) 11 | X return coroutine.yield(x + 3) + 4 12 | end 13 | 14 | X local wf = coroutine.wrap(f) 15 | X local wg = corowrap(runner.with_luacov(g)) 16 | 17 | 1 assert(wf(3) == 4) 18 | 1 assert(wf(5) == 7) 19 | 1 assert(wg(8) == 11) 20 | 1 assert(wg(10) == 14) 21 | 22 | ============================================================================== 23 | Summary 24 | ============================================================================== 25 | 26 | File Hits Missed Coverage 27 | ----------------------------- 28 | test.lua 9 0 100.00% 29 | ----------------------------- 30 | Total 9 0 100.00% 31 | -------------------------------------------------------------------------------- /spec/includeuntestedfiles/expected2.out: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | example-src/moduleA/libA.lua 3 | ============================================================================== 4 | 1 local a = 1 + 1 5 | 6 | ============================================================================== 7 | example-src/moduleA/libB.lua 8 | ============================================================================== 9 | *0 local b = 1 - math.huge 10 | 11 | ============================================================================== 12 | Summary 13 | ============================================================================== 14 | 15 | File Hits Missed Coverage 16 | ------------------------------------------------- 17 | example-src/moduleA/libA.lua 1 0 100.00% 18 | example-src/moduleA/libB.lua 0 1 0.00% 19 | ------------------------------------------------- 20 | Total 1 1 50.00% 21 | -------------------------------------------------------------------------------- /src/luacov/tick.lua: -------------------------------------------------------------------------------- 1 | _G.__SKYNET_LUACOV_COVERAGE_DATA = {} 2 | _G.__SKYNET_LUACOV_COVERAGE_DATA_WRITE_FLAG = false 3 | 4 | local skynet = require("skynet") 5 | local stats = require("luacov.stats") 6 | 7 | --- Load luacov using this if you want it to periodically 8 | -- save the stats file. This is useful if your script is 9 | -- a daemon (i.e., does not properly terminate). 10 | -- @class module 11 | -- @name luacov.tick 12 | -- @see luacov.defaults.savestepsize 13 | local runner = require("luacov.runner") 14 | runner.tick = true 15 | runner.init() 16 | 17 | --- overwrite exit 18 | local old_exit = skynet.exit 19 | local new_exit = function() 20 | stats.save(runner.configuration.statsfile, _G.__SKYNET_LUACOV_COVERAGE_DATA) 21 | old_exit() 22 | end 23 | skynet.exit = new_exit 24 | 25 | --- overwrite kill 26 | local old_kill = skynet.kill 27 | local new_kill = function() 28 | stats.save(runner.configuration.statsfile, _G.__SKYNET_LUACOV_COVERAGE_DATA) 29 | old_kill() 30 | end 31 | skynet.kill = new_kill 32 | 33 | return {} 34 | -------------------------------------------------------------------------------- /spec/hook/expected.out: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | my_hook.lua 3 | ============================================================================== 4 | 1 local runner = require "luacov.runner" 5 | 1 return function(_, line) runner.debug_hook(_, line, 3) end 6 | 7 | ============================================================================== 8 | test.lua 9 | ============================================================================== 10 | 1 local runner = require "luacov.runner" 11 | 1 local my_hook = require "my_hook" 12 | 1 debug.sethook(my_hook, "line") 13 | 1 local a = 2 14 | 1 debug.sethook(runner.debug_hook, "line") 15 | 1 local b = 3 16 | 17 | ============================================================================== 18 | Summary 19 | ============================================================================== 20 | 21 | File Hits Missed Coverage 22 | -------------------------------- 23 | my_hook.lua 2 0 100.00% 24 | test.lua 6 0 100.00% 25 | -------------------------------- 26 | Total 8 0 100.00% 27 | -------------------------------------------------------------------------------- /spec/nested/test.lua: -------------------------------------------------------------------------------- 1 | local testlib = require "testlib" 2 | local luacov = require "luacov.runner" 3 | 4 | testlib.f1() 5 | 6 | local function get_lua() 7 | local index = -1 8 | local res = "lua" 9 | 10 | while arg[index] do 11 | res = arg[index] 12 | index = index - 1 13 | end 14 | 15 | return res 16 | end 17 | 18 | local lua = get_lua() 19 | local dir_sep = package.config:sub(1, 1) 20 | 21 | local function test(tick_as_module) 22 | local config = tick_as_module and ".luacov" or "tick.luacov" 23 | local mod = tick_as_module and "luacov.tick" or "luacov" 24 | local cmd = ("%q"):format(lua) .. ' -e "package.path=[[../?.lua;../../../src/?.lua;]]..package.path"' 25 | cmd = cmd .. ' -e "osexit = os.exit"' 26 | cmd = cmd .. ' -e "require([[luacov.runner]]).load_config([[' .. config .. ']])"' 27 | cmd = cmd .. " -l " .. mod 28 | cmd = cmd .. ' -e "dofile([[script.lua]])"' 29 | cmd = cmd:gsub("/", dir_sep) 30 | 31 | local ok = os.execute("cd subdir && " .. cmd) 32 | assert(ok == 0 or ok == true) 33 | end 34 | 35 | test(true) 36 | test(false) 37 | 38 | testlib.f2() 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2007 - 2018 Hisham Muhammad. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | shallow_clone: true 2 | 3 | environment: 4 | matrix: 5 | - LUAT: "lua" 6 | LUAV: "5.1" 7 | - LUAT: "lua" 8 | LUAV: "5.2" 9 | - LUAT: "lua" 10 | LUAV: "5.3" 11 | - LUAT: "lua" 12 | LUAV: "5.4" 13 | - LUAT: "luajit" 14 | LUAV: "2.0" 15 | - LUAT: "luajit" 16 | LUAV: "2.1" 17 | 18 | before_build: 19 | - set PATH=C:\MinGW\bin;%PATH% 20 | - set PATH=C:\Python37;C:\Python37\Scripts;%PATH% # Add directory containing 'pip' to PATH 21 | - IF NOT EXIST lua_install-%LUAV%\bin\activate.bat ( pip install --upgrade certifi ) 22 | - FOR /F "tokens=* USEBACKQ" %%F IN (`python -c "import certifi;print(certifi.where())"`) DO ( SET SSL_CERT_FILE=%%F ) 23 | - IF NOT EXIST lua_install-%LUAV%\bin\activate.bat ( pip install hererocks ) 24 | - IF NOT EXIST lua_install-%LUAV%\bin\activate.bat ( hererocks lua_install-%LUAV% --%LUAT% %LUAV% %HEREROCKS_FLAGS% --luarocks latest ) 25 | - call lua_install-%LUAV%\bin\activate 26 | - luarocks install busted 27 | - luarocks install luacheck 28 | - luarocks install cluacov --deps-mode=none 29 | 30 | build_script: 31 | - luarocks make 32 | 33 | test_script: 34 | - luacheck src spec 35 | - busted 36 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | concurrency: 4 | # for PR's cancel the running task, if another commit is pushed 5 | group: ${{ github.workflow }} ${{ github.ref }} 6 | cancel-in-progress: ${{ github.event_name == 'pull_request' }} 7 | 8 | on: 9 | # build on PR and push-to-main. This works for short-lived branches, and saves 10 | # CPU cycles on duplicated tests. 11 | # For long-lived branches that diverge, you'll want to run on all pushes, not 12 | # just on push-to-main. 13 | pull_request: {} 14 | push: 15 | branches: 16 | - master 17 | 18 | 19 | jobs: 20 | lint: 21 | runs-on: ubuntu-20.04 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v3 25 | 26 | - uses: leafo/gh-actions-lua@v10 27 | with: 28 | luaVersion: "5.4" 29 | 30 | - uses: leafo/gh-actions-luarocks@v4 31 | 32 | - name: dependencies 33 | run: | 34 | luarocks install luacheck 35 | 36 | - name: lint 37 | run: | 38 | for spec in $(find . -type f -name "*.rockspec"); do 39 | (luarocks lint $spec && echo "$spec [OK]") || (echo "$spec [NOK]"; exit 1); 40 | done 41 | luacheck . 42 | -------------------------------------------------------------------------------- /src/luacov/reporter/html/static/lang-lua.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2008 Google Inc. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^(?:\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)|\'(?:[^\'\\]|\\[\s\S])*(?:\'|$))/,null,"\"'"]],[["com",/^--(?:\[(=*)\[[\s\S]*?(?:\]\1\]|$)|[^\r\n]*)/],["str",/^\[(=*)\[[\s\S]*?(?:\]\1\]|$)/],["kwd",/^(?:and|break|do|else|elseif|end|false|for|function|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b/,null],["lit",/^[+-]?(?:0x[\da-f]+|(?:(?:\.\d+|\d+(?:\.\d*)?)(?:e[+\-]?\d+)?))/i], 18 | ["pln",/^[a-z_]\w*/i],["pun",/^[^\w\t\n\r \xA0][^\w\t\n\r \xA0\"\'\-\+=]*/]]),["lua"]); 19 | -------------------------------------------------------------------------------- /.github/workflows/unix_build.yml: -------------------------------------------------------------------------------- 1 | name: "Unix build" 2 | 3 | concurrency: 4 | # for PR's cancel the running task, if another commit is pushed 5 | group: ${{ github.workflow }} ${{ github.ref }} 6 | cancel-in-progress: ${{ github.event_name == 'pull_request' }} 7 | 8 | on: 9 | # build on PR and push-to-main. This works for short-lived branches, and saves 10 | # CPU cycles on duplicated tests. 11 | # For long-lived branches that diverge, you'll want to run on all pushes, not 12 | # just on push-to-main. 13 | pull_request: {} 14 | push: 15 | branches: 16 | - master 17 | 18 | 19 | jobs: 20 | test: 21 | runs-on: ubuntu-20.04 22 | 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | luaVersion: ["5.1", "5.2", "5.3", "5.4", "luajit"] 27 | 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v3 31 | 32 | - uses: luarocks/gh-actions-lua@master 33 | with: 34 | luaVersion: ${{ matrix.luaVersion }} 35 | 36 | - uses: luarocks/gh-actions-luarocks@master 37 | 38 | - name: dependencies 39 | run: | 40 | luarocks install busted 41 | luarocks install cluacov --deps-mode=none 42 | 43 | - name: install 44 | run: | 45 | luarocks make 46 | 47 | - name: test 48 | run: | 49 | busted --Xoutput "--color" 50 | 51 | -------------------------------------------------------------------------------- /luacov-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "luacov" 2 | version = "scm-1" 3 | source = { 4 | url = "git+https://github.com/lunarmodules/luacov.git", 5 | } 6 | description = { 7 | summary = "Coverage analysis tool for Lua scripts", 8 | detailed = [[ 9 | LuaCov is a simple coverage analysis tool for Lua scripts. 10 | When a Lua script is run with the luacov module, it 11 | generates a stats file. The luacov command-line script then 12 | processes this file generating a report indicating which code 13 | paths were not traversed, which is useful for verifying the 14 | effectiveness of a test suite. 15 | ]], 16 | homepage = "https://lunarmodules.github.io/luacov/", 17 | license = "MIT" 18 | } 19 | dependencies = { 20 | "lua >= 5.1", 21 | "datafile", 22 | } 23 | build = { 24 | type = "builtin", 25 | modules = { 26 | luacov = "src/luacov.lua", 27 | ["luacov.defaults"] = "src/luacov/defaults.lua", 28 | ["luacov.hook"] = "src/luacov/hook.lua", 29 | ["luacov.linescanner"] = "src/luacov/linescanner.lua", 30 | ["luacov.reporter"] = "src/luacov/reporter.lua", 31 | ["luacov.reporter.default"] = "src/luacov/reporter/default.lua", 32 | ["luacov.reporter.html"] = "src/luacov/reporter/html.lua", 33 | ["luacov.reporter.html.template"] = "src/luacov/reporter/html/template.lua", 34 | ["luacov.runner"] = "src/luacov/runner.lua", 35 | ["luacov.stats"] = "src/luacov/stats.lua", 36 | ["luacov.tick"] = "src/luacov/tick.lua", 37 | ["luacov.util"] = "src/luacov/util.lua" 38 | }, 39 | install = { 40 | bin = { 41 | luacov = "src/bin/luacov" 42 | }, 43 | }, 44 | copy_directories = { 45 | "src/luacov/reporter/html/static", 46 | }, 47 | } 48 | -------------------------------------------------------------------------------- /src/luacov/reporter/lcov.lua: -------------------------------------------------------------------------------- 1 | -- This is a luacov reporter module that outputs the lcov tracefile format 2 | -- The format is documented in the 'geninfo' manpage 3 | 4 | local reporter = require "luacov.reporter" 5 | 6 | local md5sumhex 7 | local md5_ok, md5 = pcall(require, "md5") 8 | if md5_ok and md5.sumhexa then -- lmd5 by lhf 9 | md5sumhex = md5.sumhexa 10 | elseif md5_ok and md5.digest then -- md5 by roberto 11 | md5sumhex = md5.digest 12 | else 13 | local luaossl_ok, digest = pcall(require, "openssl.digest") 14 | if luaossl_ok then 15 | function md5sumhex(str) 16 | return (digest.new("md5"):final(str)):gsub(".", function(c) 17 | return string.format("%02x", c:byte()) 18 | end) 19 | end 20 | end 21 | end 22 | 23 | local LcovReporter = setmetatable({}, reporter.ReporterBase) 24 | LcovReporter.__index = LcovReporter 25 | 26 | function LcovReporter:on_new_file(filename) 27 | self:write("SF:", filename, "\n") 28 | end 29 | 30 | function LcovReporter:on_mis_line(filename, lineno, line) -- luacheck: no unused args 31 | self:write(string.format("DA:%d,%d", lineno, 0)) 32 | if md5sumhex then 33 | self:write(",", md5sumhex(line)) 34 | end 35 | self:write("\n") 36 | end 37 | 38 | function LcovReporter:on_hit_line(filename, lineno, line, hits) -- luacheck: no unused args 39 | self:write(string.format("DA:%d,%d", lineno, hits)) 40 | if md5sumhex then 41 | self:write(",", md5sumhex(line)) 42 | end 43 | self:write("\n") 44 | end 45 | 46 | function LcovReporter:on_end_file(filename, hits, miss) -- luacheck: no unused args 47 | self:write(string.format("LH:%d\nLF:%d\nend_of_record\n", hits, hits+miss)) 48 | end 49 | 50 | return { 51 | report = function() 52 | return reporter.report(LcovReporter) 53 | end; 54 | } -------------------------------------------------------------------------------- /src/luacov/reporter/html/static/report.js: -------------------------------------------------------------------------------- 1 | function initialize() { 2 | 3 | const LOCAL_STORAGE_KEY = "luacov_report_visible_ids"; 4 | 5 | let visibleIDs; 6 | 7 | if (localStorage) { 8 | visibleIDs = localStorage.getItem(LOCAL_STORAGE_KEY); 9 | } 10 | if (!visibleIDs) { 11 | visibleIDs = [] 12 | } else { 13 | visibleIDs = JSON.parse(visibleIDs); 14 | } 15 | 16 | function save() { 17 | if (localStorage) { 18 | localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(visibleIDs)); 19 | } 20 | } 21 | 22 | function show(id) { 23 | let classList = document.getElementById(id).classList; 24 | if (classList.contains('hidden')) { 25 | classList.remove("hidden"); 26 | if (visibleIDs.indexOf(id) < 0) { 27 | visibleIDs.push(id); 28 | save(); 29 | } 30 | } 31 | } 32 | 33 | function hide(id) { 34 | let classList = document.getElementById(id).classList; 35 | if (!classList.contains('hidden')) { 36 | classList.add("hidden"); 37 | if (visibleIDs.indexOf(id) >= 0) { 38 | visibleIDs.splice(visibleIDs.indexOf(id), 1); 39 | save(); 40 | } 41 | } 42 | } 43 | 44 | const fileHeaders = Array.prototype.slice.call(document.getElementsByTagName("h2")); 45 | fileHeaders.forEach(function (h2) { 46 | let div = h2.parentElement; 47 | let id = div.getAttribute("id"); 48 | h2.onclick = function () { 49 | if (div.classList.contains('hidden')) { 50 | show(id) 51 | } else { 52 | hide(id) 53 | } 54 | } 55 | }); 56 | 57 | let changed; 58 | visibleIDs.forEach((id) => { 59 | if (!document.getElementById(id)) { 60 | changed = true; 61 | visibleIDs.splice(visibleIDs.indexOf(id), 1); 62 | } else { 63 | show(id); 64 | } 65 | }); 66 | if (changed) { 67 | save(); 68 | } 69 | 70 | prettyPrint() 71 | } 72 | -------------------------------------------------------------------------------- /spec/includeuntestedfiles/subdir/expected.out: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | ../example-src/moduleA/libA.lua 3 | ============================================================================== 4 | 1 local a = 1 + 1 5 | 6 | ============================================================================== 7 | ../example-src/moduleA/libB.lua 8 | ============================================================================== 9 | *0 local b = 1 - math.huge 10 | 11 | ============================================================================== 12 | ../example-src/moduleB/aLib.lua 13 | ============================================================================== 14 | *0 local val = "ue" 15 | 16 | ============================================================================== 17 | ../example-src/moduleB/bLib.lua 18 | ============================================================================== 19 | *0 local test = 123 20 | 21 | ============================================================================== 22 | ../example-src/third-module/lib-1.lua 23 | ============================================================================== 24 | 1 return 5 25 | 26 | ============================================================================== 27 | ../example-src/third-module/lib-2.lua 28 | ============================================================================== 29 | 1 return 0 30 | 31 | ============================================================================== 32 | Summary 33 | ============================================================================== 34 | 35 | File Hits Missed Coverage 36 | ---------------------------------------------------------- 37 | ../example-src/moduleA/libA.lua 1 0 100.00% 38 | ../example-src/moduleA/libB.lua 0 1 0.00% 39 | ../example-src/moduleB/aLib.lua 0 1 0.00% 40 | ../example-src/moduleB/bLib.lua 0 1 0.00% 41 | ../example-src/third-module/lib-1.lua 1 0 100.00% 42 | ../example-src/third-module/lib-2.lua 1 0 100.00% 43 | ---------------------------------------------------------- 44 | Total 3 3 50.00% 45 | -------------------------------------------------------------------------------- /docs/doc/modules/luacov.tick.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | LuaCov Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 49 | 50 |
51 | 52 |

Module luacov.tick

53 |

Load luacov using this if you want it to periodically 54 | save the stats file.

55 |

This is useful if your script is 56 | a daemon (i.e., does not properly terminate).

57 |

See also:

58 | 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | 70 |
71 |
72 |
73 | generated by LDoc 1.5.0 74 | Last updated 2024-12-04 15:36:32 75 |
76 |
77 | 78 | 79 | -------------------------------------------------------------------------------- /spec/dirfilter/expected3.out: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | dirA/fileA.lua 3 | ============================================================================== 4 | 1 return "This is file A" 5 | 6 | ============================================================================== 7 | dirB/fileB.lua 8 | ============================================================================== 9 | 1 return "This is another file" 10 | 11 | ============================================================================== 12 | dirC/fileC.lua 13 | ============================================================================== 14 | 1 return "This is the final file" 15 | 16 | ============================================================================== 17 | dirC/nested/fileD.lua 18 | ============================================================================== 19 | 1 return "This is the secret nested file" 20 | 21 | ============================================================================== 22 | test.lua 23 | ============================================================================== 24 | 25 | -- This script expects the arguments "-c " 26 | 27 | -- "Unload" the luacov.runner module which was included via "-lluacov" to be able to load a specific config file 28 | *0 package.loaded["luacov.runner"] = nil 29 | 30 | -- Initialize the luacov.runner module with the luacov config file for the current test case 31 | *0 require("luacov.runner")(arg[2]) 32 | 33 | 1 require "dirA.fileA" 34 | 1 require "dirB.fileB" 35 | 1 require "dirC.fileC" 36 | 1 require "dirC.nested.fileD" 37 | 38 | ============================================================================== 39 | Summary 40 | ============================================================================== 41 | 42 | File Hits Missed Coverage 43 | ------------------------------------------ 44 | dirA/fileA.lua 1 0 100.00% 45 | dirB/fileB.lua 1 0 100.00% 46 | dirC/fileC.lua 1 0 100.00% 47 | dirC/nested/fileD.lua 1 0 100.00% 48 | test.lua 4 2 66.67% 49 | ------------------------------------------ 50 | Total 8 2 80.00% 51 | -------------------------------------------------------------------------------- /docs/doc/modules/luacov.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | LuaCov Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 49 | 50 |
51 | 52 |

Module luacov

53 |

Loads luacov.runner and immediately starts it.

54 |

Useful for launching scripts from the command-line. Returns the luacov.runner module.

55 |

Usage:

56 |
    57 |
    lua -lluacov sometest.lua
    58 | 
    59 |
60 | 61 | 62 | 63 |
64 |
65 | 66 | 67 | 68 | 69 |
70 |
71 |
72 | generated by LDoc 1.5.0 73 | Last updated 2024-12-04 15:36:32 74 |
75 |
76 | 77 | 78 | -------------------------------------------------------------------------------- /docs/README-zh.md: -------------------------------------------------------------------------------- 1 |
2 |

LuaCov-Skynet

3 | 4 |

Skynet 代码覆盖率工具

5 |
6 | 7 | [English](../README.md) 8 | 9 | ## 什么是 LuaCov-Skynet 10 | 11 | 这是基于 [LuaCov](https://github.com/lunarmodules/luacov) 改进的版本,专门用于 Skynet 框架。 12 | 13 | 原版 LuaCov 在 Skynet 的多 actor 环境中会出现数据冲突问题。这个版本通过为每个 actor 生成独立文件来解决这个问题。 14 | 15 | ## 安装方法 16 | 17 | ### 基础安装 18 | 19 | ```bash 20 | git clone https://github.com/rakuv3r/luacov-skynet.git 21 | cd luacov-skynet 22 | gcc -shared -fPIC -o src/fileutil.so src/fileutil.c 23 | ``` 24 | 25 | ### 性能优化(可选) 26 | 27 | 如需更好的性能,可安装 C 语言扩展:[cluacov-skynet](https://github.com/rakuv3r/cluacov-skynet) 28 | 29 | luacov-skynet 会自动检测并使用高性能版本。 30 | 31 | ## 如何使用 32 | 33 | ### 1. 在代码中添加 34 | 35 | ```lua 36 | require("luacov.tick") 37 | ``` 38 | 39 | ### 2. 运行程序 40 | 41 | ```bash 42 | # 启动覆盖率收集 43 | touch luacov.report 44 | 45 | # 运行你的 skynet 程序 46 | ./your_program 47 | 48 | # 生成报告 49 | luacov -r lcov -s luacov.stats.out.actor.1 50 | 51 | # 如果有多个 actor,合并结果 52 | lcov -a luacov.stats.out.actor.1 -a luacov.stats.out.actor.2 -o coverage.lcov 53 | ``` 54 | 55 | ## 主要改动 56 | 57 | - 每个 actor 生成独立的 `.actor.{id}` 文件,避免数据冲突 58 | - 通过文件创建/删除来控制覆盖率收集 59 | - 使用 C 扩展模块提高文件检测性能 60 | - 支持 LCOV 格式输出 61 | 62 | ## 配置文件 63 | 64 | 在项目根目录创建 `.luacov` 文件: 65 | 66 | ```lua 67 | return { 68 | statsfile = "luacov.stats.out", 69 | reportfile = "luacov.report.out", 70 | include = {"^src/"}, 71 | exclude = {"test/"} 72 | } 73 | ``` 74 | 75 | ## 控制方式 76 | 77 | | 操作 | 方法 | 78 | |------|------| 79 | | 开始收集 | `touch luacov.report` | 80 | | 重置数据 | `touch luacov.reset` | 81 | | 停止收集 | 删除 `luacov.doing` 文件 | 82 | 83 | ## 常见问题 84 | 85 | **编译 fileutil.so 失败** 86 | - 确保系统有 gcc 编译器 87 | - 检查是否有写权限 88 | 89 | **没有生成覆盖率数据** 90 | - 确认已创建 `luacov.doing` 文件 91 | - 检查是否正确调用了 `require("luacov.tick")` 92 | 93 | **多个 actor 数据异常** 94 | - 检查是否生成了多个 `.actor.{id}` 文件 95 | - 使用 `lcov` 命令正确合并文件 96 | 97 | ## 技术细节 98 | 99 | 程序运行时会: 100 | 1. 在全局变量中存储覆盖率数据 101 | 2. 启动 1 秒间隔的定时器检查控制文件 102 | 3. 根据文件存在状态决定保存或重置数据 103 | 4. 每个 actor 写入独立的统计文件 104 | 105 | 文件监控间隔为 1 秒,建议控制文件的操作间隔 2 秒以上。 106 | 107 | ## 许可证 108 | 109 | [MIT License](../LICENSE) -------------------------------------------------------------------------------- /src/luacov/stats.lua: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------- 2 | -- Manages the file with statistics (being) collected. 3 | -- @class module 4 | -- @name luacov.stats 5 | local stats = {} 6 | 7 | ----------------------------------------------------- 8 | -- Loads the stats file. 9 | -- @param statsfile path to the stats file. 10 | -- @return table with data or nil if couldn't load. 11 | -- The table maps filenames to stats tables. 12 | -- Per-file tables map line numbers to hits or nils when there are no hits. 13 | -- Additionally, .max field contains maximum line number 14 | -- and .max_hits contains maximum number of hits in the file. 15 | function stats.load(statsfile) 16 | local data = {} 17 | local fd = io.open(statsfile, "r") 18 | if not fd then 19 | return nil 20 | end 21 | while true do 22 | local max = fd:read("*n") 23 | if not max then 24 | break 25 | end 26 | if fd:read(1) ~= ":" then 27 | break 28 | end 29 | local filename = fd:read("*l") 30 | if not filename then 31 | break 32 | end 33 | data[filename] = { 34 | max = max, 35 | max_hits = 0 36 | } 37 | for i = 1, max do 38 | local hits = fd:read("*n") 39 | if not hits then 40 | break 41 | end 42 | if fd:read(1) ~= " " then 43 | break 44 | end 45 | if hits > 0 then 46 | data[filename][i] = hits 47 | data[filename].max_hits = math.max(data[filename].max_hits, hits) 48 | end 49 | end 50 | end 51 | fd:close() 52 | return data 53 | end 54 | 55 | ----------------------------------------------------- 56 | -- Saves data to the stats file. 57 | -- @param statsfile path to the stats file. 58 | -- @param data data to store. 59 | function stats.save(statsfile, data) 60 | local skynet = require("skynet") 61 | statsfile = statsfile .. ".actor." .. skynet.self() 62 | local fd = assert(io.open(statsfile, "w")) 63 | 64 | local filenames = {} 65 | for filename in pairs(data) do 66 | table.insert(filenames, filename) 67 | end 68 | table.sort(filenames) 69 | 70 | for _, filename in ipairs(filenames) do 71 | local filedata = data[filename] 72 | fd:write(filedata.max, ":", filename, "\n") 73 | 74 | for i = 1, filedata.max do 75 | fd:write(tostring(filedata[i] or 0), " ") 76 | end 77 | fd:write("\n") 78 | end 79 | fd:close() 80 | end 81 | 82 | return stats 83 | -------------------------------------------------------------------------------- /docs/doc/modules/luacov.hook.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | LuaCov Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 53 | 54 |
55 | 56 |

Module luacov.hook

57 |

Hook module, creates debug hook used by LuaCov.

58 |

59 | 60 |

61 | 62 | 63 |

Functions

64 | 65 | 66 | 67 | 68 | 69 |
new (runner)Creates a new debug hook.
70 | 71 |
72 |
73 | 74 | 75 |

Functions

76 | 77 |
78 |
79 | 80 | new (runner) 81 |
82 |
83 | Creates a new debug hook. 84 | 85 | 86 |

Parameters:

87 |
    88 |
  • runner 89 | runner module. 90 |
  • 91 |
92 | 93 |

Returns:

94 |
    95 | 96 | debug hook function that uses runner fields and functions 97 | and sets runner.data. 98 |
99 | 100 | 101 | 102 | 103 |
104 |
105 | 106 | 107 |
108 |
109 |
110 | generated by LDoc 1.5.0 111 | Last updated 2024-12-04 15:36:32 112 |
113 |
114 | 115 | 116 | -------------------------------------------------------------------------------- /src/luacov/hook.lua: -------------------------------------------------------------------------------- 1 | ------------------------ 2 | -- Hook module, creates debug hook used by LuaCov. 3 | -- @class module 4 | -- @name luacov.hook 5 | local hook = {} 6 | 7 | local fileutil 8 | 9 | if _G.__SKYNET_LUACOV_COVERAGE_DATA ~= nil then 10 | fileutil = require("fileutil") 11 | end 12 | 13 | ---------------------------------------------------------------- 14 | local dir_sep = package.config:sub(1, 1) 15 | if not dir_sep:find("[/\\]") then 16 | dir_sep = "/" 17 | end 18 | 19 | --- Creates a new debug hook. 20 | -- @param runner runner module. 21 | -- @return debug hook function that uses runner fields and functions 22 | -- and sets `runner.data`. 23 | function hook.new(runner) 24 | local ignored_files = {} 25 | local steps_after_save = 0 26 | 27 | return function(_, line_nr, level) 28 | -- Do not use string metamethods within the debug hook: 29 | -- they may be absent if it's called from a sandboxed environment 30 | -- or because of carelessly implemented monkey-patching. 31 | level = level or 2 32 | if not runner.initialized then 33 | return 34 | end 35 | 36 | -- Get name of processed file. 37 | local name = debug.getinfo(level, "S").source 38 | if not fileutil.file_exists(runner.configuration.report_get_file) and _G.__SKYNET_LUACOV_COVERAGE_DATA[name] and _G.__SKYNET_LUACOV_COVERAGE_DATA[name][line_nr] then 39 | return 40 | end 41 | local prefixed_name = string.match(name, "^@(.*)") 42 | if prefixed_name then 43 | name = prefixed_name:gsub("^%.[/\\]", ""):gsub("[/\\]", dir_sep) 44 | elseif not runner.configuration.codefromstrings then 45 | -- Ignore Lua code loaded from raw strings by default. 46 | return 47 | end 48 | 49 | if not fileutil.file_exists(runner.configuration.report_get_file) and _G.__SKYNET_LUACOV_COVERAGE_DATA[name] and _G.__SKYNET_LUACOV_COVERAGE_DATA[name][line_nr] then 50 | return 51 | end 52 | 53 | local data = runner.data 54 | local file = data[name] 55 | 56 | 57 | if not file then 58 | -- New or ignored file. 59 | if ignored_files[name] then 60 | return 61 | elseif runner.file_included(name) then 62 | file = {max = 0, max_hits = 0} 63 | data[name] = file 64 | else 65 | ignored_files[name] = true 66 | return 67 | end 68 | end 69 | 70 | if line_nr > file.max then 71 | file.max = line_nr 72 | end 73 | 74 | local hits = (file[line_nr] or 0) + 1 75 | file[line_nr] = hits 76 | 77 | if hits > file.max_hits then 78 | file.max_hits = hits 79 | end 80 | 81 | if runner.tick then 82 | steps_after_save = steps_after_save + 1 83 | 84 | if steps_after_save >= runner.configuration.savestepsize then 85 | steps_after_save = 0 86 | 87 | if not runner.paused then 88 | runner.save_stats() 89 | end 90 | end 91 | end 92 | end 93 | end 94 | 95 | return hook 96 | -------------------------------------------------------------------------------- /src/luacov/reporter/html/template.lua: -------------------------------------------------------------------------------- 1 | local template = {} 2 | 3 | template.SCRIPT = { 4 | "prettify.js", 5 | "lang-lua.js", 6 | "report.js" 7 | } 8 | 9 | template.STYLE = { 10 | "report.css" 11 | } 12 | 13 | -- luacheck: ignore 631 14 | template.HTML_HEADER = [[ 15 | 16 | 17 | 18 | 19 | Code coverage report 20 | 21 | 22 | {{style}} 23 | 24 | 25 |
26 | ]] 27 | 28 | template.HTML_FOOTER = [[ 29 |
30 |
31 | Code coverage generated by LuaCov at {{timestamp}} 32 |
33 | {{script}} 34 | 35 | 36 | ]] 37 | 38 | template.HTML_TOTAL = [[ 39 |
40 |

41 | Code coverage report 42 | 43 | 44 | 45 | {{coverage}}% Coverage 46 | 47 | {{hits}} Hits 48 | {{miss}} Missed 49 | 50 |

51 |
52 | 53 | ]] 54 | 55 | template.HTML_FILE_HEADER = [[ 56 | 72 | 73 | ]] 74 | 75 | template.HTML_LINE_HIT = [[{{line}}]] .. "\n" 76 | template.HTML_LINE_MIS = [[{{line}}]] .. "\n" 77 | 78 | return template 79 | -------------------------------------------------------------------------------- /spec/includeuntestedfiles/expected.out: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | example-src/moduleA/libA.lua 3 | ============================================================================== 4 | 1 local a = 1 + 1 5 | 6 | ============================================================================== 7 | example-src/moduleA/libB.lua 8 | ============================================================================== 9 | *0 local b = 1 - math.huge 10 | 11 | ============================================================================== 12 | example-src/moduleB/aLib.lua 13 | ============================================================================== 14 | *0 local val = "ue" 15 | 16 | ============================================================================== 17 | example-src/moduleB/bLib.lua 18 | ============================================================================== 19 | *0 local test = 123 20 | 21 | ============================================================================== 22 | example-src/third-module/lib-1.lua 23 | ============================================================================== 24 | 1 return 5 25 | 26 | ============================================================================== 27 | example-src/third-module/lib-2.lua 28 | ============================================================================== 29 | 1 return 0 30 | 31 | ============================================================================== 32 | subdir/test.lua 33 | ============================================================================== 34 | 35 | *0 package.path = package.path .. ";../?.lua" 36 | 37 | -- Don't load some of the modules to prevent them from getting tested/executed 38 | require "example-src.moduleA.libA" 39 | require "example-src.third-module.lib-1" 40 | require "example-src.third-module.lib-2" 41 | 42 | ============================================================================== 43 | test.lua 44 | ============================================================================== 45 | 46 | -- Don't load some of the modules to prevent them from getting tested/executed 47 | 1 require "example-src.moduleA.libA" 48 | 1 require "example-src.third-module.lib-1" 49 | 1 require "example-src.third-module.lib-2" 50 | 51 | ============================================================================== 52 | Summary 53 | ============================================================================== 54 | 55 | File Hits Missed Coverage 56 | ------------------------------------------------------- 57 | example-src/moduleA/libA.lua 1 0 100.00% 58 | example-src/moduleA/libB.lua 0 1 0.00% 59 | example-src/moduleB/aLib.lua 0 1 0.00% 60 | example-src/moduleB/bLib.lua 0 1 0.00% 61 | example-src/third-module/lib-1.lua 1 0 100.00% 62 | example-src/third-module/lib-2.lua 1 0 100.00% 63 | subdir/test.lua 0 1 0.00% 64 | test.lua 3 0 100.00% 65 | ------------------------------------------------------- 66 | Total 6 4 60.00% 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

LuaCov-Skynet

3 | 4 |

Code coverage tool for Skynet framework

5 |
6 | 7 | [中文](docs/README-zh.md) 8 | 9 | ## What is LuaCov-Skynet 10 | 11 | LuaCov-Skynet is a modified version of [LuaCov](https://github.com/lunarmodules/luacov) designed specifically for the Skynet framework. 12 | 13 | The original LuaCov has data conflicts in Skynet's multi-actor environment. This version solves the problem by creating separate files for each actor. 14 | 15 | ## Installation 16 | 17 | ### Basic Installation 18 | 19 | ```bash 20 | git clone https://github.com/rakuv3r/luacov-skynet.git 21 | cd luacov-skynet 22 | gcc -shared -fPIC -o src/fileutil.so src/fileutil.c 23 | ``` 24 | 25 | ### Performance Optimization (Optional) 26 | 27 | For better performance, install the C extension: [cluacov-skynet](https://github.com/rakuv3r/cluacov-skynet) 28 | 29 | luacov-skynet will automatically detect and use the high-performance version if available. 30 | 31 | ## How to Use 32 | 33 | ### 1. Add to Your Code 34 | 35 | ```lua 36 | require("luacov.tick") 37 | ``` 38 | 39 | ### 2. Run Your Program 40 | 41 | ```bash 42 | # Start coverage collection 43 | touch luacov.report 44 | 45 | # Run your skynet program 46 | ./your_program 47 | 48 | # Generate report 49 | luacov -r lcov -s luacov.stats.out.actor.1 50 | 51 | # For multiple actors, merge results 52 | lcov -a luacov.stats.out.actor.1 -a luacov.stats.out.actor.2 -o coverage.lcov 53 | ``` 54 | 55 | ## Key Features 56 | 57 | - Each actor creates separate `.actor.{id}` files to avoid data conflicts 58 | - Control coverage collection through file creation/deletion 59 | - Use C extension module for better file detection performance 60 | - Support LCOV format output 61 | 62 | ## Configuration 63 | 64 | Create `.luacov` file in project root: 65 | 66 | ```lua 67 | return { 68 | statsfile = "luacov.stats.out", 69 | reportfile = "luacov.report.out", 70 | include = {"^src/"}, 71 | exclude = {"test/"} 72 | } 73 | ``` 74 | 75 | ## Control Commands 76 | 77 | | Action | Method | 78 | |--------|--------| 79 | | Start collection | `touch luacov.report` | 80 | | Reset data | `touch luacov.reset` | 81 | | Stop collection | Remove `luacov.doing` file | 82 | 83 | ## Common Issues 84 | 85 | **Failed to compile fileutil.so** 86 | - Make sure gcc compiler is installed 87 | - Check write permissions 88 | 89 | **No coverage data generated** 90 | - Confirm `luacov.doing` file exists 91 | - Check if `require("luacov.tick")` is called correctly 92 | 93 | **Multiple actor data problems** 94 | - Check if multiple `.actor.{id}` files are generated 95 | - Use `lcov` command to merge files correctly 96 | 97 | ## How It Works 98 | 99 | When the program runs: 100 | 1. Store coverage data in global variables 101 | 2. Start a 1-second timer to check control files 102 | 3. Save or reset data based on file existence 103 | 4. Each actor writes to separate stats files 104 | 105 | File monitoring interval is 1 second. We recommend at least 2 seconds between control file operations. 106 | 107 | ## License 108 | 109 | [MIT License](LICENSE) -------------------------------------------------------------------------------- /docs/doc/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | LuaCov Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 46 | 47 |
48 | 49 | 50 |

Coverage analysis tool for Lua scripts

51 | 52 |

Modules

53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 82 | 83 | 84 | 85 | 86 | 87 |
luacovLoads luacov.runner and immediately starts it.
luacov.defaultsDefault values for configuration options.
luacov.hookHook module, creates debug hook used by LuaCov.
luacov.reporterReport module, will transform statistics file into a report.
luacov.runnerStatistics collecting module.
luacov.statsManages the file with statistics (being) collected.
luacov.tickLoad luacov using this if you want it to periodically 81 | save the stats file.
luacov.utilUtility module.
88 | 89 |
90 |
91 |
92 | generated by LDoc 1.5.0 93 | Last updated 2024-12-04 15:36:32 94 |
95 |
96 | 97 | 98 | -------------------------------------------------------------------------------- /src/bin/luacov: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | local runner = require("luacov.runner") 3 | local util = require("luacov.util") 4 | 5 | local patterns = {} 6 | local configfile 7 | local reporter 8 | 9 | local help_message = ([[ 10 | LuaCov %s - coverage analyzer for Lua scripts 11 | 12 | Usage: 13 | luacov [options] [pattern...] 14 | 15 | Launch your Lua programs with -lluacov to perform accounting. 16 | Launch this script to generate a report from collected stats. 17 | By default it reports on every Lua file encountered running your 18 | script. To filter filenames, pass one or more Lua patterns matching 19 | files to be included in the command line, or use a config. 20 | 21 | Options: 22 | -c filename, --config filename 23 | 24 | Use a config file, .luacov by default. For details see 25 | luacov.defaults module. 26 | 27 | -r name, --reporter name 28 | 29 | Use a custom reporter - a module in luacov.reporter.* namespace. 30 | 31 | -h, --help 32 | 33 | Show this help message and exit. 34 | 35 | Examples: 36 | luacov foo/bar 37 | 38 | This will report only on modules in the foo/bar subtree. 39 | ]]):format(runner.version) 40 | 41 | local function read_key(i) 42 | if arg[i]:sub(1, 1) ~= "-" or #arg[i] == 1 then 43 | return nil, arg[i], i + 1 44 | end 45 | 46 | if arg[i]:sub(2, 2) == "-" then 47 | local key, value = arg[i]:match("^%-%-([^=]+)=(.*)$") 48 | if key then 49 | return key, value, i + 1 50 | else 51 | return arg[i]:sub(3), arg[i + 1], i + 2 52 | end 53 | else 54 | local key = arg[i]:sub(2, 2) 55 | local value = arg[i]:sub(3) 56 | if #value == 0 then 57 | i = i + 1 58 | value = arg[i] 59 | elseif value:sub(1, 1) == "=" then 60 | value = value:sub(2) 61 | end 62 | return key, value, i + 1 63 | end 64 | end 65 | 66 | local function norm_pat(p) 67 | return (p:gsub("\\", "/"):gsub("%.lua$", "")) 68 | end 69 | 70 | local statsfile = nil 71 | 72 | local i = 1 73 | while arg[i] do 74 | local key, value 75 | key, value, i = read_key(i) 76 | if key then 77 | if (key == "h") or (key == "help") then 78 | print(help_message) 79 | os.exit(0) 80 | elseif (key == "c") or (key == "config") then 81 | configfile = value 82 | elseif (key == "r") or (key == "reporter") then 83 | reporter = value 84 | elseif (key == "s") then 85 | statsfile = value 86 | end 87 | else 88 | table.insert(patterns, norm_pat(value)) 89 | end 90 | end 91 | 92 | -- will load configfile specified, or defaults otherwise 93 | local configuration = runner.load_config(configfile) 94 | 95 | configuration.include = configuration.include or {} 96 | configuration.exclude = configuration.exclude or {} 97 | 98 | if statsfile then 99 | configuration.statsfile = statsfile 100 | split_arr = util.split(statsfile, ".") 101 | configuration.reportfile = configuration.reportfile .. ".actor." .. split_arr[#split_arr] 102 | end 103 | 104 | -- add elements specified on commandline to config 105 | for _, patt in ipairs(patterns) do 106 | table.insert(configuration.include, patt) 107 | end 108 | 109 | configuration.reporter = reporter or configuration.reporter 110 | runner.run_report(configuration) 111 | -------------------------------------------------------------------------------- /src/luacov/defaults.lua: -------------------------------------------------------------------------------- 1 | --- Default values for configuration options. 2 | -- For project specific configuration create '.luacov' file in your project 3 | -- folder. It should be a Lua script setting various options as globals 4 | -- or returning table of options. 5 | -- @class module 6 | -- @name luacov.defaults 7 | return { 8 | --- Collection coverage flag file 9 | report_doing_file = "luacov.doing", 10 | 11 | --- Get report flag file 12 | report_get_file = "luacov.report", 13 | 14 | --- Reset report flag file 15 | report_reset_file = "luacov.reset", 16 | 17 | --- Filename to store collected stats. Default: "luacov.stats.out". 18 | statsfile = "luacov.stats.out", 19 | 20 | --- Filename to store report. Default: "luacov.report.out". 21 | reportfile = "luacov.report.out", 22 | 23 | --- Enable saving coverage data after every `savestepsize` lines? 24 | -- Setting this flag to `true` in config is equivalent to running LuaCov 25 | -- using `luacov.tick` module. Default: false. 26 | tick = true, 27 | 28 | --- Stats file updating frequency for `luacov.tick`. 29 | -- The lower this value - the more frequently results will be written out to the stats file. 30 | -- You may want to reduce this value (to, for example, 2) to avoid losing coverage data in 31 | -- case your program may terminate without triggering luacov exit hooks that are supposed 32 | -- to save the data. Default: 100. 33 | savestepsize = 1, 34 | 35 | --- Run reporter on completion? Default: false. 36 | runreport = false, 37 | 38 | --- Delete stats file after reporting? Default: false. 39 | deletestats = false, 40 | 41 | --- Process Lua code loaded from raw strings? 42 | -- That is, when the 'source' field in the debug info 43 | -- does not start with '@'. Default: false. 44 | codefromstrings = false, 45 | 46 | --- Lua patterns for files to include when reporting. 47 | -- All will be included if nothing is listed. 48 | -- Do not include the '.lua' extension. Path separator is always '/'. 49 | -- Overruled by `exclude`. 50 | -- @usage 51 | -- include = { 52 | -- "mymodule$", -- the main module 53 | -- "mymodule%/.+$", -- and everything namespaced underneath it 54 | -- } 55 | include = {}, 56 | 57 | --- Lua patterns for files to exclude when reporting. 58 | -- Nothing will be excluded if nothing is listed. 59 | -- Do not include the '.lua' extension. Path separator is always '/'. 60 | -- Overrules `include`. 61 | exclude = {}, 62 | 63 | --- Table mapping names of modules to be included to their filenames. 64 | -- Has no effect if empty. 65 | -- Real filenames mentioned here will be used for reporting 66 | -- even if the modules have been installed elsewhere. 67 | -- Module name can contain '*' wildcard to match groups of modules, 68 | -- in this case corresponding path will be used as a prefix directory 69 | -- where modules from the group are located. 70 | -- @usage 71 | -- modules = { 72 | -- ["some_rock"] = "src/some_rock.lua", 73 | -- ["some_rock.*"] = "src" 74 | -- } 75 | modules = {}, 76 | 77 | --- Enable including untested files in report. 78 | -- If `true`, all untested files in "." will be included. 79 | -- If it is a table with directory and file paths, all untested files in these paths will be included. 80 | -- Note that you are not allowed to use patterns in these paths. 81 | -- Default: false. 82 | includeuntestedfiles = false, 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/luacov/util.lua: -------------------------------------------------------------------------------- 1 | local string = require("string") 2 | 3 | --------------------------------------------------- 4 | -- Utility module. 5 | -- @class module 6 | -- @name luacov.util 7 | local util = {} 8 | 9 | --- Removes a prefix from a string if it's present. 10 | -- @param str a string. 11 | -- @param prefix a prefix string. 12 | -- @return original string if does not start with prefix 13 | -- or string without prefix. 14 | function util.unprefix(str, prefix) 15 | if str:sub(1, #prefix) == prefix then 16 | return str:sub(#prefix + 1) 17 | else 18 | return str 19 | end 20 | end 21 | 22 | -- Returns contents of a file or nil + error message. 23 | local function read_file(name) 24 | local f, open_err = io.open(name, "rb") 25 | 26 | if not f then 27 | return nil, util.unprefix(open_err, name .. ": ") 28 | end 29 | 30 | local contents, read_err = f:read("*a") 31 | f:close() 32 | 33 | if contents then 34 | return contents 35 | else 36 | return nil, read_err 37 | end 38 | end 39 | 40 | --- Loads a string. 41 | -- @param str a string. 42 | -- @param[opt] env environment table. 43 | -- @param[opt] chunkname chunk name. 44 | function util.load_string(str, env, chunkname) 45 | if _VERSION:find("5%.1") then 46 | local func, err = loadstring(str, chunkname) -- luacheck: compat 47 | 48 | if not func then 49 | return nil, err 50 | end 51 | 52 | if env then 53 | setfenv(func, env) -- luacheck: compat 54 | end 55 | 56 | return func 57 | else 58 | return load(str, chunkname, "bt", env or _ENV) -- luacheck: compat 59 | end 60 | end 61 | 62 | --- Load a config file. 63 | -- Reads, loads and runs a Lua file in an environment. 64 | -- @param name file name. 65 | -- @param env environment table. 66 | -- @return true and the first return value of config on success, 67 | -- nil + error type + error message on failure, where error type 68 | -- can be "read", "load" or "run". 69 | function util.load_config(name, env) 70 | local src, read_err = read_file(name) 71 | 72 | if not src then 73 | return nil, "read", read_err 74 | end 75 | 76 | local func, load_err = util.load_string(src, env, "@config") 77 | 78 | if not func then 79 | return nil, "load", "line " .. util.unprefix(load_err, "config:") 80 | end 81 | 82 | local ok, ret = pcall(func) 83 | 84 | if not ok then 85 | return nil, "run", "line " .. util.unprefix(ret, "config:") 86 | end 87 | 88 | return true, ret 89 | end 90 | 91 | --- Checks if a file exists. 92 | -- @param name file name. 93 | -- @return true if file can be opened, false otherwise. 94 | function util.file_exists(name) 95 | local f = io.open(name) 96 | 97 | if f then 98 | f:close() 99 | return true 100 | else 101 | return false 102 | end 103 | end 104 | 105 | function util.print_table(tbl, indent) 106 | indent = indent or 0 107 | for k, v in pairs(tbl) do 108 | if type(v) == "table" then 109 | print(string.rep(" ", indent) .. k .. ": ") 110 | util.print_table(v, indent + 4) 111 | else 112 | print(string.rep(" ", indent) .. k .. ": " .. tostring(v)) 113 | end 114 | end 115 | end 116 | 117 | function util.split(str, sep) 118 | sep = sep or "%s" 119 | local parts = {} 120 | for part in string.gmatch(str, "([^" .. sep .. "]+)") do 121 | table.insert(parts, part) 122 | end 123 | return parts 124 | end 125 | 126 | return util 127 | -------------------------------------------------------------------------------- /docs/doc/modules/luacov.stats.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | LuaCov Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 53 | 54 |
55 | 56 |

Module luacov.stats

57 |

Manages the file with statistics (being) collected.

58 |

59 | 60 |

61 | 62 | 63 |

Functions

64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
load (statsfile)Loads the stats file.
save (statsfile, data)Saves data to the stats file.
74 | 75 |
76 |
77 | 78 | 79 |

Functions

80 | 81 |
82 |
83 | 84 | load (statsfile) 85 |
86 |
87 | Loads the stats file. 88 | 89 | 90 |

Parameters:

91 |
    92 |
  • statsfile 93 | path to the stats file. 94 |
  • 95 |
96 | 97 |

Returns:

98 |
    99 | 100 | table with data or nil if couldn't load. 101 | The table maps filenames to stats tables. 102 | Per-file tables map line numbers to hits or nils when there are no hits. 103 | Additionally, .max field contains maximum line number 104 | and .max_hits contains maximum number of hits in the file. 105 |
106 | 107 | 108 | 109 | 110 |
111 |
112 | 113 | save (statsfile, data) 114 |
115 |
116 | Saves data to the stats file. 117 | 118 | 119 |

Parameters:

120 |
    121 |
  • statsfile 122 | path to the stats file. 123 |
  • 124 |
  • data 125 | data to store. 126 |
  • 127 |
128 | 129 | 130 | 131 | 132 | 133 |
134 |
135 | 136 | 137 |
138 |
139 |
140 | generated by LDoc 1.5.0 141 | Last updated 2024-12-04 15:36:32 142 |
143 |
144 | 145 | 146 | -------------------------------------------------------------------------------- /docs/license.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | LuaCov - Coverage analysis for Lua scripts 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 18 |
LuaCov
19 |
Coverage analysis for Lua scripts
20 |
21 | 22 |
23 | 24 | 48 | 49 |
50 | 51 |

License

52 | 53 |

54 | LuaCov is free software: it can be used for both academic and commercial purposes 55 | at absolutely no cost. There are no royalties or GNU-like "copyleft" restrictions. 56 | LuaCov qualifies as Open Source software. 57 | Its licenses are compatible with GPL. 58 | LuaCov is not in the public domain. The legal details are below. 59 |

60 | 61 |

62 | The spirit of the license is that you are free to use LuaCov for any purpose 63 | at no cost without having to ask us. The only requirement is that if you do use 64 | LuaCov, then you should give us credit by including the appropriate copyright notice 65 | somewhere in your product or its documentation. 66 |

67 | 68 |

69 | LuaCov is designed and implemented by Hisham Muhammad. 70 | The implementation is not derived from licensed software. 71 |

72 | 73 |
74 | 75 |

Copyright (c) 2007 - 2018 Hisham Muhammad.

76 | 77 |

78 | Permission is hereby granted, free of charge, to any person obtaining a copy 79 | of this software and associated documentation files (the "Software"), to deal 80 | in the Software without restriction, including without limitation the rights 81 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 82 | copies of the Software, and to permit persons to whom the Software is 83 | furnished to do so, subject to the following conditions: 84 |

85 | 86 |

87 | The above copyright notice and this permission notice shall be included in 88 | all copies or substantial portions of the Software. 89 |

90 | 91 |

92 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 93 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 94 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 95 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 96 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 97 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 98 | THE SOFTWARE. 99 |

100 | 101 |
102 | 103 |
104 | 105 |
106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /spec/filefilter_spec.lua: -------------------------------------------------------------------------------- 1 | local dir_sep = package.config:sub(1, 1) 2 | 3 | local function assert_filefilter(config, files) 4 | package.loaded["luacov.runner"] = nil 5 | local runner = require("luacov.runner") 6 | runner.load_config(config) 7 | 8 | local expected = {} 9 | local actual = {} 10 | 11 | for filename, expected_result in pairs(files) do 12 | expected_result = expected_result and expected_result:gsub("/", dir_sep) 13 | filename = filename:gsub("/", dir_sep) 14 | local actual_result = runner.file_included(filename) and runner.real_name(filename) 15 | 16 | expected[filename] = expected_result 17 | actual[filename] = actual_result 18 | end 19 | 20 | assert.is_same(expected, actual) 21 | end 22 | 23 | describe("file filtering", function() 24 | it("excludes only luacov's own files by default", function() 25 | assert_filefilter(nil, { 26 | ["foo.lua"] = "foo.lua", 27 | ["luacov/runner.lua"] = false 28 | }) 29 | end) 30 | 31 | it("includes only files matching include patterns", function() 32 | assert_filefilter({ 33 | include = {"foo$", "bar$"} 34 | }, { 35 | ["foo.lua"] = "foo.lua", 36 | ["path/bar.lua"] = "path/bar.lua", 37 | ["baz.lua"] = false 38 | }) 39 | end) 40 | 41 | it("excludes files matching exclude patterns", function() 42 | assert_filefilter({ 43 | exclude = {"foo$", "bar$"} 44 | }, { 45 | ["foo.lua"] = false, 46 | ["path/bar.lua"] = false, 47 | ["baz.lua"] = "baz.lua" 48 | }) 49 | end) 50 | 51 | it("prioritizes exclude patterns over include patterns", function() 52 | assert_filefilter({ 53 | include = {"foo$", "bar$"}, 54 | exclude = {"foo$", "baz$"} 55 | }, { 56 | ["foo.lua"] = false, 57 | ["path/bar.lua"] = "path/bar.lua", 58 | ["path/baz.lua"] = false 59 | }) 60 | end) 61 | 62 | it("remaps paths according to module table", function() 63 | assert_filefilter({ 64 | modules = {["foo"] = "src/foo.lua"} 65 | }, { 66 | ["path/foo.lua"] = "src/foo.lua", 67 | ["bar.lua"] = false 68 | }) 69 | end) 70 | 71 | it("excludes modules matching exclude patterns", function() 72 | assert_filefilter({ 73 | modules = { 74 | ["rock.foo"] = "src/rock/foo.lua", 75 | ["rock.bar"] = "src/rock/bar.lua", 76 | ["rock.baz"] = "src/rock/baz/init.lua" 77 | }, 78 | exclude = {"bar$"} 79 | }, { 80 | ["path/rock/foo.lua"] = "src/rock/foo.lua", 81 | ["path/rock/bar.lua"] = false, 82 | ["path/rock/baz/init.lua"] = "src/rock/baz/init.lua" 83 | }) 84 | end) 85 | 86 | it("supports wildcard modules but still excludes modules matching exclude patterns", function() 87 | assert_filefilter({ 88 | modules = { 89 | ["rock"] = "src/rock.lua", 90 | ["rock.*"] = "src" 91 | }, 92 | exclude = {"bar$"} 93 | }, { 94 | ["path1/rock.lua"] = "src/rock.lua", 95 | ["path2/rock/foo.lua"] = "src/rock/foo.lua", 96 | ["path3/rock/bar.lua"] = false 97 | }) 98 | end) 99 | 100 | it("prioritizes explicit modules over wildcard modules when mapping filenames", function() 101 | assert_filefilter({ 102 | modules = { 103 | ["rock.*"] = "src", 104 | ["rock.foo"] = "foo.lua" 105 | } 106 | }, { 107 | ["path/rock/foo.lua"] = "foo.lua", 108 | ["path/rock/bar.lua"] = "src/rock/bar.lua" 109 | }) 110 | end) 111 | 112 | it("prioritizes shorter wildcard rules when mapping filenames", function() 113 | assert_filefilter({ 114 | modules = { 115 | ["rock.*"] = "src", 116 | ["rock.*.*.*"] = "src" 117 | } 118 | }, { 119 | ["path/rock/src/rock/foo.lua"] = "src/rock/foo.lua", 120 | ["path/rock/src/rock/foo/bar/baz.lua"] = "src/rock/foo/bar/baz.lua" 121 | }) 122 | end) 123 | 124 | it("prioritizes shorter explicit rules when mapping filenames", function() 125 | assert_filefilter({ 126 | modules = { 127 | ["b"] = "b1.lua", 128 | ["a.b"] = "b2.lua" 129 | } 130 | }, { 131 | ["path/b.lua"] = "b1.lua", 132 | ["path/a/b.lua"] = "b2.lua" 133 | }) 134 | 135 | assert_filefilter({ 136 | modules = { 137 | ["b"] = "b1.lua", 138 | ["c.b"] = "b2.lua" 139 | } 140 | }, { 141 | ["path/b.lua"] = "b1.lua", 142 | ["path/c/b.lua"] = "b2.lua" 143 | }) 144 | end) 145 | end) 146 | -------------------------------------------------------------------------------- /docs/doc.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #47555c; 3 | font-size: 16px; 4 | font-family: "Open Sans", sans-serif; 5 | margin: 0; 6 | padding: 0; 7 | background: #eff4ff; 8 | } 9 | 10 | a:link { 11 | color: #008fee; 12 | } 13 | 14 | a:visited { 15 | color: #008fee; 16 | } 17 | 18 | a:hover { 19 | color: #22a7ff; 20 | } 21 | 22 | h1 { 23 | font-size: 26px; 24 | } 25 | 26 | h2 { 27 | font-size: 24px; 28 | } 29 | 30 | h3 { 31 | font-size: 18px; 32 | } 33 | 34 | h4 { 35 | font-size: 16px; 36 | } 37 | 38 | hr { 39 | height: 1px; 40 | background: #c1cce4; 41 | border: 0px; 42 | margin: 20px 0; 43 | } 44 | 45 | code { 46 | font-family: "Open Sans Mono", "Andale Mono", monospace; 47 | } 48 | 49 | tt { 50 | font-family: "Open Sans Mono", "Andale Mono", monospace; 51 | } 52 | 53 | body, td, th { 54 | } 55 | 56 | textarea, pre, tt { 57 | font-family: "Open Sans Mono", "Andale Mono", monospace; 58 | } 59 | 60 | img { 61 | border-width: 0px; 62 | } 63 | 64 | .example { 65 | background-color: #323744; 66 | color: white; 67 | font-size: 16px; 68 | padding: 16px 24px; 69 | border-radius: 2px; 70 | } 71 | 72 | div.header, div.footer { 73 | } 74 | 75 | #container { 76 | } 77 | 78 | #product { 79 | background-color: white; 80 | padding: 10px; 81 | height: 125px; 82 | border-bottom: solid #d3dbec 1px; 83 | } 84 | 85 | #product big { 86 | font-size: 42px; 87 | } 88 | 89 | #product strong { 90 | font-weight: normal; 91 | } 92 | 93 | #product_logo { 94 | position: absolute; 95 | right: 0; 96 | top: 0; 97 | } 98 | 99 | #product_name { 100 | padding-top: 15px; 101 | padding-left: 30px; 102 | font-size: 42px; 103 | font-weight: normal; 104 | } 105 | 106 | #product_description { 107 | padding-left: 30px; 108 | color: #757779; 109 | } 110 | 111 | #main { 112 | background: #eff4ff; 113 | margin: 0; 114 | } 115 | 116 | #navigation { 117 | width: 100%; 118 | background-color: rgb(44, 62, 103); 119 | padding: 10px; 120 | margin: 0; 121 | } 122 | 123 | #navigation h1 { 124 | display: none; 125 | } 126 | 127 | #navigation a:hover { 128 | text-decoration: underline; 129 | } 130 | 131 | #navigation ul li a { 132 | color: rgb(136, 208, 255); 133 | font-weight: bold; 134 | text-decoration: none; 135 | } 136 | 137 | #navigation ul li li a { 138 | color: rgb(136, 208, 255); 139 | font-weight: normal; 140 | text-decoration: none; 141 | } 142 | 143 | #navigation ul { 144 | display: inline; 145 | color: white; 146 | padding: 0px; 147 | padding-top: 10px; 148 | padding-bottom: 10px; 149 | } 150 | 151 | #navigation li { 152 | display: inline; 153 | list-style-type: none; 154 | padding-left: 5px; 155 | padding-right: 5px; 156 | } 157 | 158 | #navigation li { 159 | padding: 10px; 160 | padding: 10px; 161 | } 162 | 163 | #navigation li li { 164 | } 165 | 166 | #navigation li:hover a { 167 | color: rgb(166, 238, 255); 168 | } 169 | 170 | #content { 171 | padding: 20px; 172 | width: 800px; 173 | margin-left: auto; 174 | margin-right: auto; 175 | } 176 | 177 | #about { 178 | display: none; 179 | } 180 | 181 | dl.reference { 182 | background-color: white; 183 | padding-left: 20px; 184 | padding-right: 20px; 185 | padding-bottom: 20px; 186 | border: solid #d3dbec 1px; 187 | } 188 | 189 | dl.reference dt { 190 | padding: 5px; 191 | padding-top: 25px; 192 | color: #637bbc; 193 | } 194 | 195 | dl.reference dl dt { 196 | padding-top: 5px; 197 | color: #637383; 198 | } 199 | 200 | dl.reference dd { 201 | } 202 | 203 | @media print { 204 | body { 205 | font: 10pt "Times New Roman", "TimeNR", Times, serif; 206 | } 207 | 208 | a { 209 | font-weight: bold; 210 | color: #004080; 211 | text-decoration: underline; 212 | } 213 | 214 | #main { 215 | background-color: #ffffff; 216 | border-left: 0px; 217 | } 218 | 219 | #container { 220 | margin-left: 2%; 221 | margin-right: 2%; 222 | background-color: #ffffff; 223 | } 224 | 225 | #content { 226 | margin-left: 0px; 227 | padding: 1em; 228 | border-left: 0px; 229 | border-right: 0px; 230 | background-color: #ffffff; 231 | } 232 | 233 | #navigation { 234 | display: none; 235 | } 236 | 237 | #product_logo { 238 | display: none; 239 | } 240 | 241 | #about img { 242 | display: none; 243 | } 244 | 245 | .example { 246 | font-family: "Andale Mono", monospace; 247 | font-size: 8pt; 248 | page-break-inside: avoid; 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /docs/doc/modules/luacov.util.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | LuaCov Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 53 | 54 |
55 | 56 |

Module luacov.util

57 |

Utility module.

58 |

59 | 60 |

61 | 62 | 63 |

Functions

64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 |
unprefix (str, prefix)Removes a prefix from a string if it's present.
load_string (str[, env[, chunkname]])Loads a string.
load_config (name, env)Load a config file.
file_exists (name)Checks if a file exists.
82 | 83 |
84 |
85 | 86 | 87 |

Functions

88 | 89 |
90 |
91 | 92 | unprefix (str, prefix) 93 |
94 |
95 | Removes a prefix from a string if it's present. 96 | 97 | 98 |

Parameters:

99 |
    100 |
  • str 101 | a string. 102 |
  • 103 |
  • prefix 104 | a prefix string. 105 |
  • 106 |
107 | 108 |

Returns:

109 |
    110 | 111 | original string if does not start with prefix 112 | or string without prefix. 113 |
114 | 115 | 116 | 117 | 118 |
119 |
120 | 121 | load_string (str[, env[, chunkname]]) 122 |
123 |
124 | Loads a string. 125 | 126 | 127 |

Parameters:

128 |
    129 |
  • str 130 | a string. 131 |
  • 132 |
  • env 133 | environment table. 134 | (optional) 135 |
  • 136 |
  • chunkname 137 | chunk name. 138 | (optional) 139 |
  • 140 |
141 | 142 | 143 | 144 | 145 | 146 |
147 |
148 | 149 | load_config (name, env) 150 |
151 |
152 | Load a config file. 153 | Reads, loads and runs a Lua file in an environment. 154 | 155 | 156 |

Parameters:

157 |
    158 |
  • name 159 | file name. 160 |
  • 161 |
  • env 162 | environment table. 163 |
  • 164 |
165 | 166 |

Returns:

167 |
    168 | 169 | true and the first return value of config on success, 170 | nil + error type + error message on failure, where error type 171 | can be "read", "load" or "run". 172 |
173 | 174 | 175 | 176 | 177 |
178 |
179 | 180 | file_exists (name) 181 |
182 |
183 | Checks if a file exists. 184 | 185 | 186 |

Parameters:

187 |
    188 |
  • name 189 | file name. 190 |
  • 191 |
192 | 193 |

Returns:

194 |
    195 | 196 | true if file can be opened, false otherwise. 197 |
198 | 199 | 200 | 201 | 202 |
203 |
204 | 205 | 206 |
207 |
208 |
209 | generated by LDoc 1.5.0 210 | Last updated 2024-12-04 15:36:32 211 |
212 |
213 | 214 | 215 | -------------------------------------------------------------------------------- /spec/cli_spec.lua: -------------------------------------------------------------------------------- 1 | local function get_lua() 2 | local index = -1 3 | local res = "lua" 4 | 5 | while arg[index] do 6 | res = arg[index] 7 | index = index - 1 8 | end 9 | 10 | return res 11 | end 12 | 13 | local lua = get_lua() 14 | local dir_sep = package.config:sub(1, 1) 15 | 16 | local function exec(cmd) 17 | local status = os.execute(cmd) 18 | 19 | if type(status) == "number" then 20 | assert.is_equal(0, status) 21 | else 22 | assert.is_true(status) 23 | end 24 | end 25 | 26 | local function read_file(file) 27 | local handler = assert(io.open(file)) 28 | local contents = assert(handler:read("*a")) 29 | handler:close() 30 | return contents 31 | end 32 | 33 | -- dir must be a subdir of spec/ containing expected.out or expected_file. 34 | -- The file can contain 'X' to match any number of hits. 35 | -- flags will be passed to luacov. 36 | local function assert_cli(dir, enable_cluacov, expected_file, flags, configfp) 37 | 38 | local prefix = "" 39 | local postfix = "" 40 | 41 | if configfp and dir_sep == "\\" then 42 | prefix = ("set \"LUACOV_CONFIG=%s\" &&"):format(configfp) 43 | postfix = ("& set LUACOV_CONFIG=") 44 | elseif configfp then 45 | prefix = ("LUACOV_CONFIG=%q"):format(configfp) 46 | end 47 | 48 | local test_dir = "spec" .. dir_sep .. dir 49 | local _, nestingLevel = dir:gsub("/", "") 50 | 51 | expected_file = expected_file or "expected.out" 52 | flags = flags or "" 53 | 54 | os.remove(test_dir .. dir_sep .. "luacov.stats.out") 55 | os.remove(test_dir .. dir_sep .. "luacov.report.out") 56 | 57 | finally(function() 58 | os.remove(test_dir .. dir_sep .. "luacov.stats.out") 59 | os.remove(test_dir .. dir_sep .. "luacov.report.out") 60 | end) 61 | 62 | local src_path = string.rep("../", nestingLevel + 2) .. "src" 63 | local init_lua = "package.path=[[?.lua;" .. src_path .. "/?.lua;]]..package.path; corowrap = coroutine.wrap" 64 | init_lua = init_lua:gsub("/", dir_sep) 65 | 66 | if not enable_cluacov then 67 | init_lua = init_lua .. "; package.preload[ [[cluacov.version]] ] = error" 68 | end 69 | 70 | exec(("cd %q && %s %q -e %q -lluacov test.lua %s %s"):format(test_dir, prefix, lua, init_lua, flags, postfix)) 71 | 72 | local luacov_path = (src_path .. "/bin/luacov"):gsub("/", dir_sep) 73 | exec(("cd %q && %s %q -e %q %s %s %s"):format(test_dir, prefix, lua, init_lua, luacov_path, flags, postfix)) 74 | 75 | expected_file = test_dir .. dir_sep .. expected_file 76 | local expected = read_file(expected_file) 77 | 78 | local actual_file = test_dir .. dir_sep .. "luacov.report.out" 79 | local actual = read_file(actual_file) 80 | 81 | local expected_pattern = "^" .. expected:gsub("%p", "%%%0"):gsub("X", "%%d+"):gsub("%%%/", "[/\\\\]") .. "$" 82 | 83 | assert.does_match(expected_pattern, actual) 84 | end 85 | 86 | local function register_cli_tests(enable_cluacov) 87 | describe(enable_cluacov and "cli with cluacov" or "cli without cluacov", function() 88 | if enable_cluacov and not pcall(require, "cluacov.version") then 89 | pending("cluacov not found", function() end) 90 | return 91 | end 92 | 93 | it("handles simple files", function() 94 | assert_cli("simple", enable_cluacov) 95 | end) 96 | 97 | it("handles files with shebang", function() 98 | assert_cli("shebang", enable_cluacov) 99 | end) 100 | 101 | it("handles configs using file filtering", function() 102 | assert_cli("filefilter", enable_cluacov) 103 | assert_cli("filefilter", enable_cluacov, "expected2.out", "-c 2.luacov") 104 | end) 105 | 106 | it("handles configs using directory filtering", function() 107 | assert_cli("dirfilter", enable_cluacov) 108 | assert_cli("dirfilter", enable_cluacov, "expected2.out", "-c 2.luacov") 109 | assert_cli("dirfilter", enable_cluacov, "expected3.out", "-c 3.luacov") 110 | assert_cli("dirfilter", enable_cluacov, "expected4.out", "-c 4.luacov") 111 | end) 112 | 113 | if not enable_cluacov then 114 | 115 | it("handles configs using including of untested files", function() 116 | assert_cli("includeuntestedfiles", enable_cluacov) 117 | assert_cli("includeuntestedfiles", enable_cluacov, "expected2.out", "-c 2.luacov") 118 | assert_cli("includeuntestedfiles", enable_cluacov, "expected3.out", "-c 3.luacov") 119 | assert_cli("includeuntestedfiles/subdir", enable_cluacov) 120 | end) 121 | 122 | end 123 | 124 | it("handles files using coroutines", function() 125 | assert_cli("coroutines", enable_cluacov) 126 | end) 127 | 128 | it("handles files wrapping luacov debug hook", function() 129 | assert_cli("hook", enable_cluacov) 130 | end) 131 | 132 | it("handles files that execute other files with luacov", function() 133 | assert_cli("nested", enable_cluacov) 134 | end) 135 | 136 | if enable_cluacov and _VERSION ~= "Lua 5.4" then 137 | it("handles line filtering cases solved only by cluacov", function() 138 | assert_cli("cluacov", enable_cluacov) 139 | end) 140 | end 141 | 142 | it("handles configs specified via LUACOV_CONFIG", function() 143 | assert_cli("LUACOV_CONFIG", enable_cluacov, nil, nil, "luacov.config.lua") 144 | end) 145 | 146 | end) 147 | end 148 | 149 | register_cli_tests(false) 150 | register_cli_tests(true) 151 | -------------------------------------------------------------------------------- /docs/logo/luacov.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 50 | 54 | 58 | 62 | 66 | 70 | 74 | 78 | 82 | 86 | 90 | 91 | 93 | 99 | 104 | 109 | 114 | 119 | 120 | 126 | 134 | 142 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /src/luacov/reporter/html/static/report.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap'); 2 | 3 | html { 4 | height: 100%; 5 | } 6 | 7 | body { 8 | position: relative; 9 | margin: 0; 10 | font-family: 'Roboto Mono', monospace; 11 | display: flex; 12 | flex-direction: column; 13 | min-height: 100%; 14 | } 15 | 16 | main { 17 | flex: 1 0 auto; 18 | } 19 | 20 | footer { 21 | flex-shrink: 0; 22 | font-size: 12px; 23 | color: #808080; 24 | width: 100%; 25 | height: 30px; 26 | line-height: 30px; 27 | text-align: center; 28 | } 29 | 30 | .pln { 31 | color: #000 32 | } 33 | 34 | @media screen { 35 | .str { 36 | color: #080 37 | } 38 | 39 | .kwd { 40 | color: #008 41 | } 42 | 43 | .com { 44 | color: #800 45 | } 46 | 47 | .typ { 48 | color: #606 49 | } 50 | 51 | .lit { 52 | color: #066 53 | } 54 | 55 | .clo, .opn, .pun { 56 | color: #660 57 | } 58 | 59 | .tag { 60 | color: #008 61 | } 62 | 63 | .atn { 64 | color: #606 65 | } 66 | 67 | .atv { 68 | color: #080 69 | } 70 | 71 | .dec, .var { 72 | color: #606 73 | } 74 | 75 | .fun { 76 | color: red 77 | } 78 | } 79 | 80 | @media print, projection { 81 | .kwd, .tag, .typ { 82 | font-weight: 700 83 | } 84 | 85 | .str { 86 | color: #060 87 | } 88 | 89 | .kwd { 90 | color: #006 91 | } 92 | 93 | .com { 94 | color: #600; 95 | font-style: italic 96 | } 97 | 98 | .typ { 99 | color: #404 100 | } 101 | 102 | .lit { 103 | color: #044 104 | } 105 | 106 | .clo, .opn, .pun { 107 | color: #440 108 | } 109 | 110 | .tag { 111 | color: #006 112 | } 113 | 114 | .atn { 115 | color: #404 116 | } 117 | 118 | .atv { 119 | color: #060 120 | } 121 | } 122 | 123 | pre.prettyprint { 124 | padding: 0; 125 | margin: 0; 126 | border: 0; 127 | border-bottom: 1px solid #808080; 128 | } 129 | 130 | .file.hidden pre.prettyprint { 131 | display: none; 132 | } 133 | 134 | ol.linenums { 135 | margin-top: 0; 136 | margin-bottom: 0; 137 | counter-reset: number; 138 | list-style-type: none; 139 | } 140 | 141 | pre.prettyprint ol { 142 | margin: 0; 143 | padding: 0; 144 | } 145 | 146 | pre.prettyprint li { 147 | font-size: 14px; 148 | word-wrap: normal; 149 | white-space: pre; 150 | line-height: 20px; 151 | } 152 | 153 | pre.prettyprint li::before { 154 | counter-increment: number; 155 | content: counter(number) "\a0"; 156 | float: left; 157 | position: relative; 158 | width: 1%; 159 | min-width: 50px; 160 | padding-right: 10px; 161 | padding-left: 10px; 162 | font-size: 12px; 163 | color: #6E7781; 164 | text-align: right; 165 | white-space: nowrap; 166 | vertical-align: top; 167 | line-height: 20px; 168 | } 169 | 170 | pre.prettyprint span.fc { 171 | background-color: #DFF0D8; 172 | display: block; 173 | } 174 | 175 | pre.prettyprint span.fc:before { 176 | content: attr(data-hits); 177 | position: absolute; 178 | left: 0; 179 | width: 30px; 180 | margin-left: 5px; 181 | text-align: center; 182 | font-size: 10px; 183 | color: #808080; 184 | } 185 | 186 | pre.prettyprint span.nc { 187 | background-color: #F2DEDE; 188 | display: block; 189 | } 190 | 191 | pre.prettyprint span.pc { 192 | background-color: #FFFFCC; 193 | display: block; 194 | } 195 | 196 | pre.prettyprint span.bpc:hover { 197 | background-color: #FFFF80; 198 | } 199 | 200 | pre.prettyprint span.hits { 201 | position: absolute; 202 | left: 0; 203 | width: 30px; 204 | margin-left: 5px; 205 | text-align: center; 206 | font-size: 10px; 207 | } 208 | 209 | 210 | .file .title { 211 | border-bottom: 1px solid #808080; 212 | border-left: 0; 213 | border-right: 0; 214 | padding: 0.67em; 215 | margin: 0; 216 | position: relative; 217 | } 218 | 219 | .file:not(.total) .title { 220 | cursor: pointer; 221 | } 222 | 223 | .file:not(.total) .title:before { 224 | content: ""; 225 | display: block; 226 | width: 100%; 227 | height: 100%; 228 | position: absolute; 229 | left: 0; 230 | right: 0; 231 | bottom: 0; 232 | top: 0; 233 | background: #fff; 234 | opacity: 0.0; 235 | transition: all 200ms; 236 | } 237 | 238 | .file:not(.total) .title:not(.total):hover:before { 239 | opacity: 0.3; 240 | } 241 | 242 | .file.danger .title { 243 | background: #F2DEDE; 244 | } 245 | 246 | .file.warning .title { 247 | background: #FCF8E3; 248 | } 249 | 250 | .file.success-low .title { 251 | background: #DFF0D8; 252 | } 253 | 254 | .file.success-medium .title { 255 | background: #C3E3B5; 256 | } 257 | 258 | .file.success-high .title { 259 | background: #99CB84; 260 | } 261 | 262 | .file .title .stats { 263 | font-size: 12px; 264 | line-height: 12px; 265 | color: #444; 266 | margin-top: 2px; 267 | display: flex; 268 | } 269 | 270 | .file .title .stats .hits, 271 | .file .title .stats .cov, 272 | .file .title .stats .miss { 273 | padding: 2px 6px; 274 | border-radius: 12px; 275 | border: 1px solid #808080; 276 | position: relative; 277 | margin-right: 10px; 278 | } 279 | 280 | .file .title .stats .hits { 281 | background: #DFF0D8; 282 | } 283 | 284 | .file .title .stats .cov { 285 | background: #F2DEDE; 286 | overflow: hidden; 287 | } 288 | 289 | .file .title .stats .cov span { 290 | position: relative; 291 | } 292 | 293 | .file .title .stats .cov .bg { 294 | height: 100%; 295 | position: absolute; 296 | left: 0; 297 | top: 0; 298 | background-color: #DFF0D8; 299 | } 300 | 301 | .file .title .stats .miss { 302 | background: #F2DEDE; 303 | } 304 | -------------------------------------------------------------------------------- /docs/doc/ldoc.css: -------------------------------------------------------------------------------- 1 | /* BEGIN RESET 2 | 3 | Copyright (c) 2010, Yahoo! Inc. All rights reserved. 4 | Code licensed under the BSD License: 5 | http://developer.yahoo.com/yui/license.html 6 | version: 2.8.2r1 7 | */ 8 | html { 9 | color: #000; 10 | background: #FFF; 11 | } 12 | body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td { 13 | margin: 0; 14 | padding: 0; 15 | } 16 | table { 17 | border-collapse: collapse; 18 | border-spacing: 0; 19 | } 20 | fieldset,img { 21 | border: 0; 22 | } 23 | address,caption,cite,code,dfn,em,strong,th,var,optgroup { 24 | font-style: inherit; 25 | font-weight: inherit; 26 | } 27 | del,ins { 28 | text-decoration: none; 29 | } 30 | li { 31 | margin-left: 20px; 32 | } 33 | caption,th { 34 | text-align: left; 35 | } 36 | h1,h2,h3,h4,h5,h6 { 37 | font-size: 100%; 38 | font-weight: bold; 39 | } 40 | q:before,q:after { 41 | content: ''; 42 | } 43 | abbr,acronym { 44 | border: 0; 45 | font-variant: normal; 46 | } 47 | sup { 48 | vertical-align: baseline; 49 | } 50 | sub { 51 | vertical-align: baseline; 52 | } 53 | legend { 54 | color: #000; 55 | } 56 | input,button,textarea,select,optgroup,option { 57 | font-family: inherit; 58 | font-size: inherit; 59 | font-style: inherit; 60 | font-weight: inherit; 61 | } 62 | input,button,textarea,select {*font-size:100%; 63 | } 64 | /* END RESET */ 65 | 66 | body { 67 | margin-left: 1em; 68 | margin-right: 1em; 69 | font-family: arial, helvetica, geneva, sans-serif; 70 | background-color: #ffffff; margin: 0px; 71 | } 72 | 73 | code, tt { font-family: monospace; font-size: 1.1em; } 74 | span.parameter { font-family:monospace; } 75 | span.parameter:after { content:":"; } 76 | span.types:before { content:"("; } 77 | span.types:after { content:")"; } 78 | .type { font-weight: bold; font-style:italic } 79 | 80 | body, p, td, th { font-size: .95em; line-height: 1.2em;} 81 | 82 | p, ul { margin: 10px 0 0 0px;} 83 | 84 | strong { font-weight: bold;} 85 | 86 | em { font-style: italic;} 87 | 88 | h1 { 89 | font-size: 1.5em; 90 | margin: 20px 0 20px 0; 91 | } 92 | h2, h3, h4 { margin: 15px 0 10px 0; } 93 | h2 { font-size: 1.25em; } 94 | h3 { font-size: 1.15em; } 95 | h4 { font-size: 1.06em; } 96 | 97 | a:link { font-weight: bold; color: #004080; text-decoration: none; } 98 | a:visited { font-weight: bold; color: #006699; text-decoration: none; } 99 | a:link:hover { text-decoration: underline; } 100 | 101 | hr { 102 | color:#cccccc; 103 | background: #00007f; 104 | height: 1px; 105 | } 106 | 107 | blockquote { margin-left: 3em; } 108 | 109 | ul { list-style-type: disc; } 110 | 111 | p.name { 112 | font-family: "Andale Mono", monospace; 113 | padding-top: 1em; 114 | } 115 | 116 | pre { 117 | background-color: rgb(245, 245, 245); 118 | border: 1px solid #C0C0C0; /* silver */ 119 | padding: 10px; 120 | margin: 10px 0 10px 0; 121 | overflow: auto; 122 | font-family: "Andale Mono", monospace; 123 | } 124 | 125 | pre.example { 126 | font-size: .85em; 127 | } 128 | 129 | table.index { border: 1px #00007f; } 130 | table.index td { text-align: left; vertical-align: top; } 131 | 132 | #container { 133 | margin-left: 1em; 134 | margin-right: 1em; 135 | background-color: #f0f0f0; 136 | } 137 | 138 | #product { 139 | text-align: center; 140 | border-bottom: 1px solid #cccccc; 141 | background-color: #ffffff; 142 | } 143 | 144 | #product big { 145 | font-size: 2em; 146 | } 147 | 148 | #main { 149 | background-color: #f0f0f0; 150 | border-left: 2px solid #cccccc; 151 | } 152 | 153 | #navigation { 154 | float: left; 155 | width: 14em; 156 | vertical-align: top; 157 | background-color: #f0f0f0; 158 | overflow: visible; 159 | } 160 | 161 | #navigation h2 { 162 | background-color:#e7e7e7; 163 | font-size:1.1em; 164 | color:#000000; 165 | text-align: left; 166 | padding:0.2em; 167 | border-top:1px solid #dddddd; 168 | border-bottom:1px solid #dddddd; 169 | } 170 | 171 | #navigation ul 172 | { 173 | font-size:1em; 174 | list-style-type: none; 175 | margin: 1px 1px 10px 1px; 176 | } 177 | 178 | #navigation li { 179 | text-indent: -1em; 180 | display: block; 181 | margin: 3px 0px 0px 22px; 182 | } 183 | 184 | #navigation li li a { 185 | margin: 0px 3px 0px -1em; 186 | } 187 | 188 | #content { 189 | margin-left: 14em; 190 | padding: 1em; 191 | width: 700px; 192 | border-left: 2px solid #cccccc; 193 | border-right: 2px solid #cccccc; 194 | background-color: #ffffff; 195 | } 196 | 197 | #about { 198 | clear: both; 199 | padding: 5px; 200 | border-top: 2px solid #cccccc; 201 | background-color: #ffffff; 202 | } 203 | 204 | @media print { 205 | body { 206 | font: 12pt "Times New Roman", "TimeNR", Times, serif; 207 | } 208 | a { font-weight: bold; color: #004080; text-decoration: underline; } 209 | 210 | #main { 211 | background-color: #ffffff; 212 | border-left: 0px; 213 | } 214 | 215 | #container { 216 | margin-left: 2%; 217 | margin-right: 2%; 218 | background-color: #ffffff; 219 | } 220 | 221 | #content { 222 | padding: 1em; 223 | background-color: #ffffff; 224 | } 225 | 226 | #navigation { 227 | display: none; 228 | } 229 | pre.example { 230 | font-family: "Andale Mono", monospace; 231 | font-size: 10pt; 232 | page-break-inside: avoid; 233 | } 234 | } 235 | 236 | table.module_list { 237 | border-width: 1px; 238 | border-style: solid; 239 | border-color: #cccccc; 240 | border-collapse: collapse; 241 | } 242 | table.module_list td { 243 | border-width: 1px; 244 | padding: 3px; 245 | border-style: solid; 246 | border-color: #cccccc; 247 | } 248 | table.module_list td.name { background-color: #f0f0f0; min-width: 200px; } 249 | table.module_list td.summary { width: 100%; } 250 | 251 | 252 | table.function_list { 253 | border-width: 1px; 254 | border-style: solid; 255 | border-color: #cccccc; 256 | border-collapse: collapse; 257 | } 258 | table.function_list td { 259 | border-width: 1px; 260 | padding: 3px; 261 | border-style: solid; 262 | border-color: #cccccc; 263 | } 264 | table.function_list td.name { background-color: #f0f0f0; min-width: 200px; } 265 | table.function_list td.summary { width: 100%; } 266 | 267 | ul.nowrap { 268 | overflow:auto; 269 | white-space:nowrap; 270 | } 271 | 272 | dl.table dt, dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;} 273 | dl.table dd, dl.function dd {padding-bottom: 1em; margin: 10px 0 0 20px;} 274 | dl.table h3, dl.function h3 {font-size: .95em;} 275 | 276 | /* stop sublists from having initial vertical space */ 277 | ul ul { margin-top: 0px; } 278 | ol ul { margin-top: 0px; } 279 | ol ol { margin-top: 0px; } 280 | ul ol { margin-top: 0px; } 281 | 282 | /* make the target distinct; helps when we're navigating to a function */ 283 | a:target + * { 284 | background-color: #FF9; 285 | } 286 | 287 | 288 | /* styles for prettification of source */ 289 | pre .comment { color: #558817; } 290 | pre .constant { color: #a8660d; } 291 | pre .escape { color: #844631; } 292 | pre .keyword { color: #aa5050; font-weight: bold; } 293 | pre .library { color: #0e7c6b; } 294 | pre .marker { color: #512b1e; background: #fedc56; font-weight: bold; } 295 | pre .string { color: #8080ff; } 296 | pre .number { color: #f8660d; } 297 | pre .function-name { color: #60447f; } 298 | pre .operator { color: #2239a8; font-weight: bold; } 299 | pre .preprocessor, pre .prepro { color: #a33243; } 300 | pre .global { color: #800080; } 301 | pre .user-keyword { color: #800080; } 302 | pre .prompt { color: #558817; } 303 | pre .url { color: #272fc2; text-decoration: underline; } 304 | 305 | -------------------------------------------------------------------------------- /src/luacov/reporter/html.lua: -------------------------------------------------------------------------------- 1 | local datafile = require("datafile") 2 | local luacov_reporter = require("luacov.reporter") 3 | 4 | local reporter = {} 5 | 6 | local HTML_HEADER, HTML_FOOTER, HTML_TOTAL, HTML_FILE_HEADER, HTML_FILE_FOOTER, HTML_LINE_HIT, HTML_LINE_MIS 7 | 8 | local function parse_template(template, values) 9 | local content = template:gsub("{{([a-z_]+)}}", function(key) 10 | return values[key] 11 | end) 12 | return content 13 | end 14 | 15 | ---------------------------------------------------------------- 16 | --- parse template 17 | do 18 | local template = require("luacov.reporter.html.template") 19 | 20 | --- Removes a prefix from a string if it's present. 21 | -- @param str a string. 22 | -- @param prefix a prefix string. 23 | -- @return original string if does not start with prefix 24 | -- or string without prefix. 25 | local function unprefix(str, prefix) 26 | if str:sub(1, #prefix) == prefix then 27 | return str:sub(#prefix + 1) 28 | else 29 | return str 30 | end 31 | end 32 | 33 | -- Returns contents of a file or nil + error message. 34 | local function read_asset(name) 35 | local f, open_err = datafile.open("src/luacov/reporter/html/static/" .. name, "rb") 36 | 37 | if not f then 38 | error(unprefix(open_err, name .. ": ")) 39 | end 40 | 41 | local contents, read_err = f:read("*a") 42 | f:close() 43 | 44 | if contents then 45 | return contents 46 | else 47 | error(read_err) 48 | end 49 | end 50 | 51 | local asset_types = { 52 | script = template.SCRIPT, 53 | style = template.STYLE, 54 | } 55 | 56 | local assets_content = {} 57 | for tag, assets in pairs(asset_types) do 58 | for _, name in ipairs(assets) do 59 | local content = read_asset(name) 60 | if (not assets_content[tag]) then 61 | assets_content[tag] = "" 62 | end 63 | if (tag == "script") then 64 | assets_content[tag] = assets_content[tag] .. "\n