├── .busted ├── .editorconfig ├── .github └── workflows │ ├── lint.yml │ └── unix_build.yml ├── .gitignore ├── .luacheckrc ├── LICENSE ├── README.md ├── appveyor.yml ├── config.ld ├── docs ├── doc.css ├── doc │ ├── index.html │ ├── ldoc.css │ └── modules │ │ ├── luacov.defaults.html │ │ ├── luacov.hook.html │ │ ├── luacov.html │ │ ├── luacov.reporter.html │ │ ├── luacov.runner.html │ │ ├── luacov.stats.html │ │ ├── luacov.tick.html │ │ └── luacov.util.html ├── index.html ├── license.html ├── logo │ ├── luacov-120x120.png │ ├── luacov-144x144.png │ ├── luacov-src.svg │ ├── luacov.png │ └── luacov.svg ├── luacov-html-reporter.png └── luacov.png ├── luacov-scm-1.rockspec ├── spec ├── LUACOV_CONFIG │ ├── expected.out │ ├── luacov.config.lua │ ├── test.lua │ ├── test1.lua │ └── test2.lua ├── cli_spec.lua ├── cluacov │ ├── expected.out │ └── test.lua ├── coroutines │ ├── expected.out │ └── test.lua ├── dirfilter │ ├── .luacov │ ├── 2.luacov │ ├── 3.luacov │ ├── 4.luacov │ ├── dirA │ │ └── fileA.lua │ ├── dirB │ │ └── fileB.lua │ ├── dirC │ │ ├── fileC.lua │ │ └── nested │ │ │ └── fileD.lua │ ├── expected.out │ ├── expected2.out │ ├── expected3.out │ ├── expected4.out │ └── test.lua ├── filefilter │ ├── .luacov │ ├── 2.luacov │ ├── expected.out │ ├── expected2.out │ ├── test.lua │ ├── test2.lua │ └── test3.lua ├── filefilter_spec.lua ├── hook │ ├── expected.out │ ├── my_hook.lua │ └── test.lua ├── includeuntestedfiles │ ├── .luacov │ ├── 2.luacov │ ├── 3.luacov │ ├── example-src │ │ ├── moduleA │ │ │ ├── libA.lua │ │ │ └── libB.lua │ │ ├── moduleB │ │ │ ├── aLib.lua │ │ │ └── bLib.lua │ │ └── third-module │ │ │ ├── lib-1.lua │ │ │ └── lib-2.lua │ ├── expected.out │ ├── expected2.out │ ├── expected3.out │ ├── subdir │ │ ├── .luacov │ │ ├── expected.out │ │ └── test.lua │ └── test.lua ├── linescanner_spec.lua ├── nested │ ├── .luacov │ ├── expected.out │ ├── subdir │ │ ├── .luacov │ │ ├── script.lua │ │ └── tick.luacov │ ├── test.lua │ └── testlib.lua ├── shebang │ ├── expected.out │ └── test.lua └── simple │ ├── expected.out │ └── test.lua └── src ├── bin └── luacov ├── luacov.lua └── luacov ├── defaults.lua ├── hook.lua ├── linescanner.lua ├── reporter.lua ├── reporter ├── default.lua ├── html.lua └── html │ ├── static │ ├── lang-lua.js │ ├── prettify.js │ ├── report.css │ └── report.js │ └── template.lua ├── runner.lua ├── stats.lua ├── tick.lua └── util.lua /.busted: -------------------------------------------------------------------------------- 1 | return { 2 | default = { 3 | verbose = true, 4 | output = "gtest", 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | /luarocks 47 | /lua 48 | /lua_modules 49 | /.luarocks 50 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

LuaCov

3 | 4 |

5 | Coverage analyzer for Lua 6 |

7 |
8 | 9 |
10 | 11 | ## Status 12 | [![Unix build](https://img.shields.io/github/actions/workflow/status/lunarmodules/luacov/unix_build.yml?branch=master&label=Unix%20build&logo=linux)](https://github.com/lunarmodules/luacov/actions/workflows/unix_build.yml) 13 | [![Windows build](https://ci.appveyor.com/api/projects/status/nwlc6603cva412ub?svg=true)](https://ci.appveyor.com/project/hishamhm/luacov) 14 | [![Lint](https://github.com/lunarmodules/luacov/workflows/Lint/badge.svg)](https://github.com/lunarmodules/luacov/actions/workflows/lint.yml) 15 | [![SemVer](https://img.shields.io/github/v/tag/lunarmodules/luacov?color=brightgreen&label=SemVer&logo=semver&sort=semver)](CHANGELOG.md) 16 | 17 | ## Overview 18 | 19 | LuaCov is a simple coverage analyzer for [Lua](http://www.lua.org) scripts. 20 | When a Lua script is run with the `luacov` module loaded, it generates a stats 21 | file with the number of executions of each line of the script and its loaded 22 | modules. The `luacov` command-line script then processes this file generating 23 | a report file which allows one to visualize which code paths were not 24 | traversed, which is useful for verifying the effectiveness of a test suite. 25 | 26 | LuaCov is free software and, like Lua, is released under the 27 | [MIT License](https://www.lua.org/license.html). 28 | 29 | ## Download and Installation 30 | 31 | LuaCov can be downloaded from its 32 | [Github downloads page](https://github.com/lunarmodules/luacov/releases). 33 | 34 | It can also be installed using Luarocks: 35 | 36 | ``` 37 | luarocks install luacov 38 | ``` 39 | 40 | In order to additionally install experimental C extensions that improve 41 | performance and analysis accuracy install 42 | [CLuaCov](https://github.com/mpeterv/cluacov) package instead: 43 | 44 | ``` 45 | luarocks install cluacov 46 | ``` 47 | 48 | LuaCov is written in pure Lua and has no external dependencies. 49 | 50 | ## Instructions 51 | 52 | Using LuaCov consists of two steps: running your script to collect coverage 53 | data, and then running `luacov` on the collected data to generate a report 54 | (see [configuration](#configuration) below for other options). 55 | 56 | To collect coverage data, your script needs to load the `luacov` Lua module. 57 | This can be done from the command-line, without modifying your script, like 58 | this: 59 | 60 | lua -lluacov test.lua 61 | 62 | Alternatively, you can add `require("luacov")` to the first line of your 63 | script. 64 | 65 | Once the script is run, a file called `luacov.stats.out` is generated. If the 66 | file already exists, statistics are _added_ to it. This is useful, for 67 | example, for making a series of runs with different input parameters in a test 68 | suite. To start the accounting from scratch, just delete the stats file. 69 | 70 | To generate a report, just run the `luacov` command-line script. It expects to 71 | find a file named `luacov.stats.out` in the current directory, and outputs a 72 | file named `luacov.report.out`. The script takes the following parameters: 73 | 74 | luacov [-c=configfile] [filename...] 75 | 76 | For the `-c` option see below at [configuration](#configuration). The filenames 77 | (actually Lua patterns) indicate the files to include in the report, specifying 78 | them here equals to adding them to the `include` list in the configuration 79 | file, with `.lua` extension stripped. 80 | 81 | This is an example output of the report file: 82 | 83 | ``` 84 | ============================================================================== 85 | test.lua 86 | ============================================================================== 87 | 1 if 10 > 100 then 88 | *0 print("I don't think this line will execute.") 89 | else 90 | 1 print("Hello, LuaCov!") 91 | end 92 | ``` 93 | 94 | Note that to generate this report, `luacov` reads the source files. Therefore, 95 | it expects to find them in the same location they were when the `luacov` 96 | module ran (the stats file stores the filenames, but not the sources 97 | themselves). 98 | 99 | To silence missed line reporting for a group of lines, place inline options 100 | `luacov: disable` and `luacov: enable` in short comments around them: 101 | 102 | ```lua 103 | if SOME_DEBUG_CONDITION_THAT_IS_ALWAYS_FALSE_IN_TESTS then 104 | -- luacov: disable 105 | 106 | -- Lines here are not marked as missed even though they are not covered. 107 | 108 | -- luacov: enable 109 | end 110 | ``` 111 | 112 | LuaCov saves its stats upon normal program termination. If your program is a 113 | daemon -- in other words, if it does not terminate normally -- you can use the 114 | `luacov.tick` module or `tick` configuration option, which periodically saves 115 | the stats file. For example, to run (on Unix systems) LuaCov on 116 | [Xavante](httpsf://lunarmodules.github.io/xavante/), just modify the first line 117 | of `xavante_start.lua` so it reads: 118 | 119 | ``` 120 | #!/usr/bin/env lua -lluacov.tick 121 | ``` 122 | 123 | or add 124 | 125 | ```lua 126 | tick = true 127 | ``` 128 | 129 | to `.luacov` config file. 130 | 131 | 132 | ## Configuration 133 | 134 | LuaCov includes several configuration options, which have their defaults 135 | stored in `src/luacov/defaults.lua`. These are the global defaults. To use 136 | project specific configuration, create a Lua script setting options as globals 137 | or returning a table with some options and store it as `.luacov` in the project 138 | directory from where `luacov` is being run. Alternatively, store it elsewhere 139 | and specify the path in the `LUACOV_CONFIG` environment variable. 140 | 141 | For example, this config informs LuaCov that only `foo` module and its 142 | submodules should be covered and that they are located inside `src` directory: 143 | 144 | ```lua 145 | modules = { 146 | ["foo"] = "src/foo/init.lua", 147 | ["foo.*"] = "src" 148 | } 149 | ``` 150 | 151 | For a full list of options, see 152 | [`luacov.defaults` documentation](https://lunarmodules.github.io/luacov/doc/modules/luacov.defaults.html). 153 | 154 | ## Html reporter 155 | 156 | To generate report file as html document, adjust the `.luacov` parameters to 157 | 158 | ```lua 159 | reporter = "html" 160 | reportfile = "luacov.report.html" 161 | ``` 162 | 163 | ![LuaCov Html Reporter](docs/luacov-html-reporter.png) 164 | 165 | ## Custom reporter engines 166 | 167 | LuaCov supports custom reporter engines, which are distributed as separate 168 | packages. Check them out! 169 | 170 | * Cobertura: https://github.com/britzl/luacov-cobertura 171 | * Coveralls: https://github.com/moteus/luacov-coveralls 172 | * Console: https://github.com/spacewander/luacov-console 173 | * LCOV: https://github.com/daurnimator/luacov-reporter-lcov 174 | 175 | ## Using development version 176 | 177 | After cloning this repo, these commands may be useful: 178 | 179 | * `luarocks make` to install LuaCov from local sources; 180 | * `busted` to run tests using [busted](https://github.com/Olivine-Labs/busted). 181 | * `ldoc .` to regenerate documentation using 182 | [LDoc](https://github.com/stevedonovan/LDoc). 183 | * `luacheck .` to lint using [Luacheck](https://github.com/lunarmodules/luacheck). 184 | 185 | ## Credits 186 | 187 | LuaCov was designed and implemented by Hisham Muhammad as a tool for testing 188 | [LuaRocks](https://luarocks.org/). 189 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/doc/modules/luacov.defaults.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 | 54 | 55 |
56 | 57 |

Module luacov.defaults

58 |

Default values for configuration options.

59 |

For project specific configuration create '.luacov' file in your project 60 | folder. It should be a Lua script setting various options as globals 61 | or returning table of options.

62 | 63 | 64 |

Tables

65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
includeLua patterns for files to include when reporting.
excludeLua patterns for files to exclude when reporting.
modulesTable mapping names of modules to be included to their filenames.
79 |

Fields

80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
statsfileFilename to store collected stats.
reportfileFilename to store report.
tickEnable saving coverage data after every savestepsize lines?
savestepsizeStats file updating frequency for luacov.tick.
runreportRun reporter on completion?
deletestatsDelete stats file after reporting?
codefromstringsProcess Lua code loaded from raw strings?
includeuntestedfilesEnable including untested files in report.
114 | 115 |
116 |
117 | 118 | 119 |

Tables

120 | 121 |
122 |
123 | 124 | include 125 |
126 |
127 | Lua patterns for files to include when reporting. 128 | All will be included if nothing is listed. 129 | Do not include the '.lua' extension. Path separator is always '/'. 130 | Overruled by exclude. 131 | 132 | 133 | 134 | 135 | 136 | 137 |

Usage:

138 |
    139 |
    include = {
    140 |    "mymodule$",      -- the main module
    141 |    "mymodule%/.+$",  -- and everything namespaced underneath it
    142 | }
    143 |
144 | 145 |
146 |
147 | 148 | exclude 149 |
150 |
151 | Lua patterns for files to exclude when reporting. 152 | Nothing will be excluded if nothing is listed. 153 | Do not include the '.lua' extension. Path separator is always '/'. 154 | Overrules include. 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 |
163 |
164 | 165 | modules 166 |
167 |
168 | Table mapping names of modules to be included to their filenames. 169 | Has no effect if empty. 170 | Real filenames mentioned here will be used for reporting 171 | even if the modules have been installed elsewhere. 172 | Module name can contain '*' wildcard to match groups of modules, 173 | in this case corresponding path will be used as a prefix directory 174 | where modules from the group are located. 175 | 176 | 177 | 178 | 179 | 180 | 181 |

Usage:

182 |
    183 |
    modules = {
    184 |    ["some_rock"] = "src/some_rock.lua",
    185 |    ["some_rock.*"] = "src"
    186 | }
    187 |
188 | 189 |
190 |
191 |

Fields

192 | 193 |
194 |
195 | 196 | statsfile 197 |
198 |
199 | Filename to store collected stats. Default: "luacov.stats.out". 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 |
208 |
209 | 210 | reportfile 211 |
212 |
213 | Filename to store report. Default: "luacov.report.out". 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 |
222 |
223 | 224 | tick 225 |
226 |
227 | Enable saving coverage data after every savestepsize lines? 228 | Setting this flag to true in config is equivalent to running LuaCov 229 | using luacov.tick module. Default: false. 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 |
238 |
239 | 240 | savestepsize 241 |
242 |
243 | Stats file updating frequency for luacov.tick. 244 | The lower this value - the more frequently results will be written out to the stats file. 245 | You may want to reduce this value (to, for example, 2) to avoid losing coverage data in 246 | case your program may terminate without triggering luacov exit hooks that are supposed 247 | to save the data. Default: 100. 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 |
256 |
257 | 258 | runreport 259 |
260 |
261 | Run reporter on completion? Default: false. 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 |
270 |
271 | 272 | deletestats 273 |
274 |
275 | Delete stats file after reporting? Default: false. 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 |
284 |
285 | 286 | codefromstrings 287 |
288 |
289 | Process Lua code loaded from raw strings? 290 | That is, when the 'source' field in the debug info 291 | does not start with '@'. Default: false. 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 |
300 |
301 | 302 | includeuntestedfiles 303 |
304 |
305 | Enable including untested files in report. 306 | If true, all untested files in "." will be included. 307 | If it is a table with directory and file paths, all untested files in these paths will be included. 308 | Note that you are not allowed to use patterns in these paths. 309 | Default: false. 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 |
318 |
319 | 320 | 321 |
322 |
323 |
324 | generated by LDoc 1.5.0 325 | Last updated 2024-12-04 15:36:32 326 |
327 |
328 | 329 | 330 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/doc/modules/luacov.reporter.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 | 54 | 55 |
56 | 57 |

Module luacov.reporter

58 |

Report module, will transform statistics file into a report.

59 |

60 | 61 |

62 | 63 | 64 |

Functions

65 | 66 | 67 | 68 | 69 | 70 |
report ([reporter_class])Runs the report generator.
71 |

Class ReporterBase

72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 |
ReporterBase:config ()Returns configuration table.
ReporterBase:max_hits ()Returns maximum number of hits per line in all coverage data.
ReporterBase:write (...)Writes strings to report file.
ReporterBase:files ()Returns array of filenames to be reported.
ReporterBase:stats (filename)Returns coverage data for a file.
ReporterBase:on_start ()Stub method called before reporting.
ReporterBase:on_new_file (filename)Stub method called before processing a file.
ReporterBase:on_file_error (filename, error_type, message)Stub method called if a file couldn't be processed due to an error.
ReporterBase:on_empty_line (filename, lineno, line)Stub method called for each empty source line 108 | and other lines that can't be hit.
ReporterBase:on_mis_line (filename, lineno, line)Stub method called for each missed source line.
ReporterBase:on_hit_line (filename, lineno, line, hits)Stub method called for each hit source line.
ReporterBase:on_end_file (filename, hits, miss)Stub method called after a file has been processed.
ReporterBase:on_end ()Stub method called after reporting.
127 | 128 |
129 |
130 | 131 | 132 |

Functions

133 | 134 |
135 |
136 | 137 | report ([reporter_class]) 138 |
139 |
140 | Runs the report generator. 141 | To load a config, use luacov.runner.load_config first. 142 | 143 | 144 |

Parameters:

145 |
    146 |
  • reporter_class 147 | custom reporter class. Will be 148 | instantiated using 'new' method with configuration 149 | (see luacov.defaults) as the argument. It should 150 | return nil + error if something went wrong. 151 | After acquiring a reporter object its 'run' and 'close' 152 | methods will be called. 153 | The easiest way to implement a custom reporter class is to 154 | extend ReporterBase. 155 | (optional) 156 |
  • 157 |
158 | 159 | 160 | 161 | 162 | 163 |
164 |
165 |

Class ReporterBase

166 | 167 |
168 | Basic reporter class stub. 169 | Implements 'new', 'run' and 'close' methods required by report. 170 | Provides some helper methods and stubs to be overridden by child classes. 171 |
172 |

Usage:

173 |
local MyReporter = setmetatable({}, ReporterBase)
174 | MyReporter.__index = MyReporter
175 | function MyReporter:on_hit_line(...)
176 |    self:write(("File %s: hit line %s %d times"):format(...))
177 | end
178 |
179 |
180 | 181 | ReporterBase:config () 182 |
183 |
184 | Returns configuration table. 185 | 186 | 187 | 188 | 189 | 190 |

See also:

191 | 194 | 195 | 196 |
197 |
198 | 199 | ReporterBase:max_hits () 200 |
201 |
202 | Returns maximum number of hits per line in all coverage data. 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 |
211 |
212 | 213 | ReporterBase:write (...) 214 |
215 |
216 | Writes strings to report file. 217 | 218 | 219 |

Parameters:

220 |
    221 |
  • ... 222 | strings. 223 |
  • 224 |
225 | 226 | 227 | 228 | 229 | 230 |
231 |
232 | 233 | ReporterBase:files () 234 |
235 |
236 | Returns array of filenames to be reported. 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 |
245 |
246 | 247 | ReporterBase:stats (filename) 248 |
249 |
250 | Returns coverage data for a file. 251 | 252 | 253 |

Parameters:

254 |
    255 |
  • filename 256 | name of the file. 257 |
  • 258 |
259 | 260 | 261 | 262 |

See also:

263 | 266 | 267 | 268 |
269 |
270 | 271 | ReporterBase:on_start () 272 |
273 |
274 | Stub method called before reporting. 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 |
283 |
284 | 285 | ReporterBase:on_new_file (filename) 286 |
287 |
288 | Stub method called before processing a file. 289 | 290 | 291 |

Parameters:

292 |
    293 |
  • filename 294 | name of the file. 295 |
  • 296 |
297 | 298 | 299 | 300 | 301 | 302 |
303 |
304 | 305 | ReporterBase:on_file_error (filename, error_type, message) 306 |
307 |
308 | Stub method called if a file couldn't be processed due to an error. 309 | 310 | 311 |

Parameters:

312 |
    313 |
  • filename 314 | name of the file. 315 |
  • 316 |
  • error_type 317 | "open", "read" or "load". 318 |
  • 319 |
  • message 320 | error message. 321 |
  • 322 |
323 | 324 | 325 | 326 | 327 | 328 |
329 |
330 | 331 | ReporterBase:on_empty_line (filename, lineno, line) 332 |
333 |
334 | Stub method called for each empty source line 335 | and other lines that can't be hit. 336 | 337 | 338 |

Parameters:

339 |
    340 |
  • filename 341 | name of the file. 342 |
  • 343 |
  • lineno 344 | line number. 345 |
  • 346 |
  • line 347 | the line itself as a string. 348 |
  • 349 |
350 | 351 | 352 | 353 | 354 | 355 |
356 |
357 | 358 | ReporterBase:on_mis_line (filename, lineno, line) 359 |
360 |
361 | Stub method called for each missed source line. 362 | 363 | 364 |

Parameters:

365 |
    366 |
  • filename 367 | name of the file. 368 |
  • 369 |
  • lineno 370 | line number. 371 |
  • 372 |
  • line 373 | the line itself as a string. 374 |
  • 375 |
376 | 377 | 378 | 379 | 380 | 381 |
382 |
383 | 384 | ReporterBase:on_hit_line (filename, lineno, line, hits) 385 |
386 |
387 | Stub method called for each hit source line. 388 | 389 | 390 |

Parameters:

391 |
    392 |
  • filename 393 | name of the file. 394 |
  • 395 |
  • lineno 396 | line number. 397 |
  • 398 |
  • line 399 | the line itself as a string. 400 |
  • 401 |
  • hits 402 | number of times the line was hit. Should be positive. 403 |
  • 404 |
405 | 406 | 407 | 408 | 409 | 410 |
411 |
412 | 413 | ReporterBase:on_end_file (filename, hits, miss) 414 |
415 |
416 | Stub method called after a file has been processed. 417 | 418 | 419 |

Parameters:

420 |
    421 |
  • filename 422 | name of the file. 423 |
  • 424 |
  • hits 425 | total number of hit lines in the file. 426 |
  • 427 |
  • miss 428 | total number of missed lines in the file. 429 |
  • 430 |
431 | 432 | 433 | 434 | 435 | 436 |
437 |
438 | 439 | ReporterBase:on_end () 440 |
441 |
442 | Stub method called after reporting. 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 |
451 |
452 | 453 | 454 |
455 |
456 |
457 | generated by LDoc 1.5.0 458 | Last updated 2024-12-04 15:36:32 459 |
460 |
461 | 462 | 463 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | LuaCov - Coverage analysis for Lua scripts 6 | 7 | 8 | 22 | 23 | 24 | 25 | 26 |
27 | 28 |
29 | 32 |
LuaCov
33 |
Coverage analysis for Lua scripts
34 |
35 | 36 |
37 | 38 | 62 | 63 |
64 | 65 |

Overview

66 | 67 |

68 | LuaCov is a simple coverage analyzer for Lua 69 | scripts. When a Lua script is run with the luacov module loaded, it 70 | generates a stats file with the number of executions of each line of the 71 | script and its loaded modules. The luacov command-line script then 72 | processes this file generating a report file which allows one to visualize 73 | which code paths were not traversed, which is useful for verifying the 74 | effectiveness of a test suite. 75 |

76 | 77 |

78 | LuaCov is free software and uses the same license as Lua (MIT). 79 |

80 | 81 |

Download

82 | 83 |

84 | LuaCov can be downloaded via LuaRocks: 85 |

86 | 87 |
 88 | luarocks install luacov
 89 | 
90 | 91 |

92 | There are some C extensions LuaCov can use (if they are available) to improve performance 93 | and analysis accuracy. To install LuaCov with these extensions install 94 | CLuaCov package instead: 95 |

96 | 97 |
 98 | luarocks install cluacov
 99 | 
100 | 101 |

102 | LuaCov itself is written in pure Lua and has no external dependencies. 103 |

104 | 105 |

106 | You can also get the code directly from the git repo. 107 |

108 | 109 |

Instructions

110 | 111 |

112 | Using LuaCov consists of two steps: running your script to collect 113 | coverage data, and then running luacov on the collected data to 114 | generate a report. 115 |

116 | 117 |

118 | To collect coverage data, your script needs to load the luacov 119 | Lua module. This can be done from the command-line, without modifying 120 | your script, like this: 121 |

122 | 123 |
124 | lua -lluacov test.lua
125 | 
126 | 127 |

128 |      or 129 |

130 | 131 |
132 | lua -erequire('luacov.runner')('myconfigfilename') test.lua
133 | 
134 | 135 |

136 | (Alternatively, you can add require("luacov") to the first line 137 | of your script.) 138 |

139 | 140 |

141 | Once the script is run, a file called luacov.stats.out is generated. 142 | If the file already exists, statistics are added to it. This is useful, 143 | for example, for making a series of runs with different input parameters in 144 | a test suite. To start the accounting from scratch, just delete the stats file. 145 |

146 | 147 |

148 | To generate a report, just run the luacov command-line script. 149 | It expects to find a file named luacov.stats.out in the current 150 | directory, and outputs a file named luacov.report.out. 151 |

152 | 153 |

This is an example output of the report file:

154 | 155 |
156 | ==============================================================================
157 | test.lua
158 | ==============================================================================
159 |  1 if 10 > 100 then
160 | *0    print("I don't think this line will execute.")
161 |    else
162 |  1    print("Hello, LuaCov!")
163 |    end
164 | 
165 | 166 |

167 | Note that to generate this report, luacov reads the source files. 168 | Therefore, it expects to find them in the same location they were when 169 | the luacov module ran (the stats file stores the filenames, but 170 | not the sources themselves). 171 |

172 | 173 |

174 | To silence missed line reporting for a group of lines, place inline options 175 | luacov: disable and luacov: enable in short comments around them: 176 |

177 | 178 |
179 | if SOME_DEBUG_CONDITION_THAT_IS_ALWAYS_FALSE_IN_TESTS then
180 |    -- luacov: disable
181 | 
182 |    -- Lines here are not marked as missed even though they are not covered.
183 | 
184 |    -- luacov: enable
185 | end
186 | 
187 | 188 |

189 | LuaCov saves its stats upon normal program termination. If your program 190 | is a daemon -- in other words, if it does not terminate normally -- you 191 | can use the luacov.tick module or the tick configuration option, 192 | which periodically saves the stats file. For example, to run (on Unix systems) 193 | LuaCov on Xavante, 194 | just modify the first line of xavante_start.lua so it reads: 195 |

196 | 197 |
198 | #!/usr/bin/env lua -lluacov.tick
199 | 
200 | 201 | or add this to .luacov config file: 202 | 203 |
204 | tick = true
205 | 
206 | 207 | LuaCov includes several configuration options, which have their defaults 208 | stored in luacov.defaults module. 209 | These are the global defaults. To use project specific configuration, create a Lua script 210 | setting options as globals or returning a table with some options and store it as 211 | .luacov in the project directory from where luacov is being run. 212 | For example, this config informs LuaCov that only foo module and its submodules 213 | should be covered and that they are located inside src directory: 214 | 215 |
216 | modules = {
217 |    ["foo"] = "src/foo/init.lua",
218 |    ["foo.*"] = "src"
219 | }
220 | 
221 | 222 |

History

223 | 224 |
225 |
0.16.0 [Dec 04, 2024]
226 |
227 |
    228 |
  • HTML reporter
  • 229 |
  • use $LUACOV_CONFIG as the default config file
  • 230 |
  • Exclude goto statements and labels from accounting
  • 231 |
232 |
233 | 234 |
0.15.0 [Feb 15, 2021]
235 |
236 |
    237 |
  • Lua 5.4 support (without CLuaCov)
  • 238 |
  • Fixes in the feature for including untested files: 239 |
      240 |
    • paths are correctly normalized
    • 241 |
    • the stats object format is corrected.
    • 242 |
    • the include config option is honored
    • 243 |
    244 |
  • 245 |
  • The includeuntestedfiles now accepts either true or a table of files and directories to check
  • 246 |
247 |
248 |
0.14.0 [Jan 28, 2020]
249 |
250 |
    251 |
  • Added option to include untested files in the report
  • 252 |
  • Reduce probability of interrupt errors when running LuaCov in a subprocess
  • 253 |
254 |
255 |
0.13.0 [May 5, 2018]
256 |
257 |
    258 |
  • Added luacov: disable and luacov: enable inline options that mark source lines between them as impossible to hit.
  • 259 |
  • Fixed error when reporing coverage for files with a shebang lines using CLuaCov.
  • 260 |
261 |
262 |
0.12.0 [June 29, 2016]
263 |
264 |
    265 |
  • Added support for experimental C extensions (CLuaCov).
  • 266 |
  • Changed config format: options are now set by assigning to globals, old format (returning a table) is still supported.
  • 267 |
  • Added tickconfig option, equivalent to using luacov.tick module.
  • 268 |
  • Fixed coverage data being saved to wrong file when using relative statsfile path and the program running LuaCov changes directories.
  • 269 |
  • Improved config loading error handling.
  • 270 |
  • Added :on_file_error() stub method to base reporter class, used for reporting problems when analyzing coverage data related to a file.
  • 271 |
272 |
273 |
0.11.0 [April 18, 2016]
274 |
275 |
    276 |
  • Fixed several cases of lines falsely reported as missed.
  • 277 |
  • Fixed luacov.tick module not working.
  • 278 |
  • Improved default reporter output format.
  • 279 |
  • Reduced coverage collection overhead.
  • 280 |
  • Changed how coverage is saved, it's now possible to start a child Lua process with LuaCov enabled without wrapping the launch in luacov.pause and luacov.resume in the parent. 281 |
  • Several minor fixes and improvements.
  • 282 |
283 |
284 |
0.10.0 [February 9, 2016]
285 |
286 |
    287 |
  • Added debug_hook() function for use in custom debug hooks.
  • 288 |
  • Fixed patterns passed as command-line arguments matching too much.
  • 289 |
  • Fixed order in which module name translations are applied
  • 290 |
291 |
292 |
0.9.1 [December 7, 2015]
293 |
294 |
    295 |
  • Fixed error when running LuaCov using Lua 5.1.
  • 296 |
297 |
298 |
0.9 [December 6, 2015]
299 |
300 |
    301 |
  • with_luacov() function for covering coroutines created via the C API.
  • 302 |
  • fix priorities in modules list
  • 303 |
  • improve coverage analysis/exclusions list
  • 304 |
  • improve handling of multiline function declarations
  • 305 |
  • LDoc documentation
  • 306 |
307 |
308 |
0.8 [September 30, 2015]
309 |
310 |
    311 |
  • Improved lexer which reduces false positives
  • 312 |
  • luacov.pause() and luacov.resume() functions
  • 313 |
  • "modules" option for configuration
  • 314 |
  • Plus several fixes and code cleanups.
  • 315 |
316 |
317 |
0.7 [January 12, 2015]
318 |
319 |
    320 |
  • Improvement in detection of long strings.
  • 321 |
  • Added "savestepsize" option.
  • 322 |
  • Fix handling "codefromstring" option.
  • 323 |
324 |
325 |
0.6 [September 10, 2014]
326 |
327 |
    328 |
  • Support for custom reporter objects
  • 329 |
  • Configuration option for processing/skipping strings
  • 330 |
  • Several fixes: behavior of on_exit, inclusion/exclusions lists, etc.
  • 331 |
332 |
333 |
0.5 [February 8, 2014]
334 |
335 |
    336 |
  • Improved performance in reporter module
  • 337 |
  • More improvements in exclusions list
  • 338 |
339 |
340 |
0.4 [December 3, 2013]
341 |
342 |
    343 |
  • Lua 5.2 compatibility fixes
  • 344 |
  • Several improvements in exclusions list
  • 345 |
346 |
347 |
0.3 [October 10, 2012]
348 |
349 |
    350 |
  • Added configuration options and files
  • 351 |
  • Summary in report
  • 352 |
  • Improved handling of long strings and comments
  • 353 |
  • Support for coroutines and os.exit()
  • 354 |
355 |
356 |
0.2 [April 30, 2009]
357 |
358 |
    359 |
  • Ignore code loaded from strings.
  • 360 |
361 |
362 |
0.1 [July 16, 2007]
363 |
364 |
    365 |
  • Initial release.
  • 366 |
367 |
368 |
369 | 370 |

Credits

371 | 372 |

373 | LuaCov was originally designed and implemented by Hisham Muhammad as 374 | a tool for testing LuaRocks. A number 375 | of people have improved it since: see the Git logs for the full list of 376 | contributors! 377 |

378 | 379 |
380 | 381 |
382 | 383 |
384 | 385 | 386 | 387 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/logo/luacov-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunarmodules/luacov/7c2238df934c871b113bfeb0ec6c4b9130d56603/docs/logo/luacov-120x120.png -------------------------------------------------------------------------------- /docs/logo/luacov-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunarmodules/luacov/7c2238df934c871b113bfeb0ec6c4b9130d56603/docs/logo/luacov-144x144.png -------------------------------------------------------------------------------- /docs/logo/luacov.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunarmodules/luacov/7c2238df934c871b113bfeb0ec6c4b9130d56603/docs/logo/luacov.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/luacov-html-reporter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunarmodules/luacov/7c2238df934c871b113bfeb0ec6c4b9130d56603/docs/luacov-html-reporter.png -------------------------------------------------------------------------------- /docs/luacov.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunarmodules/luacov/7c2238df934c871b113bfeb0ec6c4b9130d56603/docs/luacov.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/LUACOV_CONFIG/luacov.config.lua: -------------------------------------------------------------------------------- 1 | include = { "test2" } 2 | -------------------------------------------------------------------------------- /spec/LUACOV_CONFIG/test.lua: -------------------------------------------------------------------------------- 1 | require "test1" 2 | require "test2" 3 | -------------------------------------------------------------------------------- /spec/LUACOV_CONFIG/test1.lua: -------------------------------------------------------------------------------- 1 | local a = 1 2 | -------------------------------------------------------------------------------- /spec/LUACOV_CONFIG/test2.lua: -------------------------------------------------------------------------------- 1 | local b = 2 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/cluacov/test.lua: -------------------------------------------------------------------------------- 1 | local s = "some text" 2 | .. "some other" 3 | return s 4 | -------------------------------------------------------------------------------- /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/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/.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/3.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | -- No custom include patterns, should include all files 3 | } 4 | -------------------------------------------------------------------------------- /spec/dirfilter/4.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | -- Filter via modules config option 3 | modules = { 4 | ["dirA.*"] = ".", 5 | ["dirB.*"] = "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /spec/dirfilter/dirA/fileA.lua: -------------------------------------------------------------------------------- 1 | return "This is file A" 2 | -------------------------------------------------------------------------------- /spec/dirfilter/dirB/fileB.lua: -------------------------------------------------------------------------------- 1 | return "This is another file" 2 | -------------------------------------------------------------------------------- /spec/dirfilter/dirC/fileC.lua: -------------------------------------------------------------------------------- 1 | return "This is the final file" 2 | -------------------------------------------------------------------------------- /spec/dirfilter/dirC/nested/fileD.lua: -------------------------------------------------------------------------------- 1 | return "This is the secret nested file" 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /spec/filefilter/.luacov: -------------------------------------------------------------------------------- 1 | include = {"test$", "test2"} 2 | -------------------------------------------------------------------------------- /spec/filefilter/2.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | include = {"test$", "test2$"}, 3 | exclude = {"test$", "test3$"} 4 | } 5 | -------------------------------------------------------------------------------- /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/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/filefilter/test.lua: -------------------------------------------------------------------------------- 1 | require "test2" 2 | require "test3" 3 | -------------------------------------------------------------------------------- /spec/filefilter/test2.lua: -------------------------------------------------------------------------------- 1 | local a = 1 2 | -------------------------------------------------------------------------------- /spec/filefilter/test3.lua: -------------------------------------------------------------------------------- 1 | local b = 2 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/hook/my_hook.lua: -------------------------------------------------------------------------------- 1 | local runner = require "luacov.runner" 2 | return function(_, line) runner.debug_hook(_, line, 3) end 3 | -------------------------------------------------------------------------------- /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/.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | -- All files implicitly included 3 | includeuntestedfiles = true 4 | } 5 | -------------------------------------------------------------------------------- /spec/includeuntestedfiles/2.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | include = { 3 | "example%-src%/moduleA%/*" 4 | }, 5 | 6 | includeuntestedfiles = { 7 | "example-src/moduleA" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /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/example-src/moduleA/libA.lua: -------------------------------------------------------------------------------- 1 | local a = 1 + 1 2 | -------------------------------------------------------------------------------- /spec/includeuntestedfiles/example-src/moduleA/libB.lua: -------------------------------------------------------------------------------- 1 | local b = 1 - math.huge 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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/includeuntestedfiles/subdir/.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | include = { 3 | "..%/example%-src/*" 4 | }, 5 | includeuntestedfiles = { 6 | "../example-src" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/linescanner_spec.lua: -------------------------------------------------------------------------------- 1 | local LineScanner = require("luacov.linescanner") 2 | 3 | local function assert_linescan(expected_source) 4 | local scanner = LineScanner:new() 5 | assert.is_table(scanner) 6 | 7 | local actual_lines = {} 8 | 9 | for expected_line in expected_source:gmatch("([^\n]*)\n") do 10 | local line = expected_line:sub(1, -2) 11 | 12 | local always_excluded, excluded_when_not_hit = scanner:consume(line) 13 | local actual_symbol = always_excluded and "-" or (excluded_when_not_hit and "?" or "+") 14 | local actual_line = line .. actual_symbol 15 | table.insert(actual_lines, actual_line) 16 | end 17 | 18 | local actual_source = table.concat(actual_lines, "\n") .. "\n" 19 | 20 | assert.is_equal(expected_source, actual_source) 21 | end 22 | 23 | describe("linescanner", function() 24 | it("includes simple calls", function() 25 | assert_linescan([[ 26 | print("test") + 27 | ]]) 28 | end) 29 | 30 | it("excludes shebang line", function() 31 | assert_linescan([[ 32 | #!/usr/bin/env rm - 33 | #!another one + 34 | ]]) 35 | end) 36 | 37 | it("is not sure if assignment of nil to a local should be included", function() 38 | assert_linescan([[ 39 | local thing = nil ? 40 | ]]) 41 | end) 42 | 43 | it("inludes function assignments", function() 44 | assert_linescan([[ 45 | local stuff = function (x) return x end + 46 | ]]) 47 | end) 48 | 49 | it("handles dangling paren after table end (same line)", function() 50 | assert_linescan([[ 51 | local thing = stuff({ + 52 | b = { name = 'bob', ? 53 | }, ? 54 | -- comment - 55 | }) ? 56 | ]]) 57 | end) 58 | 59 | it("handles dangling paren after table end (next line)", function() 60 | assert_linescan([[ 61 | local thing = stuff({ + 62 | b = { name = 'bob', ? 63 | }, ? 64 | -- comment - 65 | } ? 66 | ) ? 67 | ]]) 68 | end) 69 | 70 | it("excludes 'if true then'", function() 71 | assert_linescan([[ 72 | if true then - 73 | print("test") + 74 | end - 75 | ]]) 76 | end) 77 | 78 | it("excludes 'while true do'", function() 79 | assert_linescan([[ 80 | while true do - 81 | print("test") + 82 | break ? 83 | end - 84 | ]]) 85 | end) 86 | 87 | it("excludes 'if', 'then', and 'end' with nothing else on the line", function() 88 | assert_linescan([[ 89 | local a, b = 1,2 + 90 | if - 91 | a < b + 92 | then - 93 | a = b + 94 | end - 95 | print("test") + 96 | ]]) 97 | end) 98 | 99 | it("excludes 'end' followed by semicolons or parens", function() 100 | assert_linescan([[ 101 | local a,b = 1,2 + 102 | if a < b then + 103 | a = b + 104 | end; - 105 | - 106 | local function foo(f) f() end + 107 | foo(function() + 108 | a = b + 109 | end) - 110 | - 111 | print(not_end) + 112 | ]]) 113 | end) 114 | 115 | it("is not sure about 'return function()'", function() 116 | assert_linescan([[ 117 | local function foo(f) - 118 | return function() ? 119 | a = a + 120 | end - 121 | end - 122 | foo()() + 123 | ]]) 124 | end) 125 | 126 | it("is not sure about table fields", function() 127 | -- Line 'c = 3' should be excluded, but can not because it looks like an assignment. 128 | assert_linescan([[ 129 | local s = { + 130 | a = 1; ? 131 | b = 2, ? 132 | c = 3 + 133 | } ? 134 | ]]) 135 | end) 136 | 137 | it("includes 'return ..., function()'", function() 138 | -- There is a rule that intends to exclude lines like 'return 1, 2, function()', but it is bugged (see #27). 139 | -- TODO: remove that rule. 140 | assert_linescan([[ 141 | local function foo(f) - 142 | return 1, 2, function() + 143 | a = a + 144 | end - 145 | end - 146 | local a,b,c = foo() + 147 | c() + 148 | ]]) 149 | end) 150 | 151 | it("excludes lines within long strings", function() 152 | assert_linescan([=[ 153 | print([[ + 154 | some long string - 155 | ]==] - 156 | still going - 157 | end]]) + 158 | ]=]) 159 | end) 160 | 161 | it("excludes lines within assigned long strings, and doubts the assignment", function() 162 | assert_linescan([=[ 163 | local s = [[ ? 164 | abc - 165 | ]] + 166 | ]=]) 167 | 168 | assert_linescan([=[ 169 | s = [[ ? 170 | abc - 171 | ]] + 172 | ]=]) 173 | 174 | assert_linescan([=[ 175 | t.k = [[ ? 176 | abc - 177 | ]] + 178 | ]=]) 179 | 180 | assert_linescan([=[ 181 | t.k1.k2 = [[ + 182 | abc - 183 | ]] + 184 | ]=]) 185 | 186 | assert_linescan([=[ 187 | t["k"] = [[ ? 188 | abc - 189 | ]] + 190 | ]=]) 191 | end) 192 | 193 | it("ignores inline long comments", function() 194 | assert_linescan([=[ 195 | local function foo(--[[unused]] x) - 196 | return x + 197 | end - 198 | ]=]) 199 | end) 200 | 201 | it("applies same rules even when whitespace is strange", function() 202 | assert_linescan([=[ 203 | while true do - 204 | end - 205 | ]=]) 206 | end) 207 | 208 | it("ignores lines within short strings", function() 209 | assert_linescan([=[ 210 | local a = "[[\ ? 211 | ]]\ - 212 | print(b)" + 213 | print(a) + 214 | ]=]) 215 | 216 | assert_linescan([=[ 217 | local a = ("\ ? 218 | local function(") + 219 | ]=]) 220 | 221 | assert_linescan([=[ 222 | local a = ([[ ? 223 | format %string - 224 | ]]):format(var) + 225 | ]=]) 226 | end) 227 | 228 | it("ignores function declarations spanning multiple lines", function() 229 | assert_linescan([[ 230 | local function fff(a, - 231 | b, - 232 | c) - 233 | return a + b + c + 234 | end - 235 | ]]) 236 | 237 | assert_linescan([[ 238 | local function fff( - 239 | a, b, c) - 240 | return a + b + c + 241 | end - 242 | ]]) 243 | 244 | assert_linescan([[ 245 | local function fff - 246 | (a, b, c) - 247 | return a + b + c + 248 | end - 249 | ]]) 250 | end) 251 | 252 | it("handles local declarations", function() 253 | assert_linescan([[ 254 | local function f() end + 255 | local x - 256 | local x, y - 257 | local x = - 258 | 1 ? 259 | local x, y = - 260 | 2, 3 + 261 | local x, y = 2, + 262 | 3 ? 263 | local x = ( ? 264 | a + b) + 265 | local x, y = ( ? 266 | a + b), c + 267 | ]]) 268 | 269 | assert_linescan([[ 270 | local x = nil ? 271 | - 272 | for i = 1, 100 do + 273 | x = 1 + 274 | end - 275 | ]]) 276 | end) 277 | 278 | it("handles multiline declarations in tables", function() 279 | assert_linescan([=[ 280 | local t = { + 281 | ["1"] = function() ? 282 | foo() + 283 | end, - 284 | ["2"] = ([[ ? 285 | %s - 286 | ]]):format(var), + 287 | ["3"] = [[ ? 288 | bar() - 289 | ]] + 290 | } ? 291 | ]=]) 292 | end) 293 | 294 | it("handles single expressions in tables and calls", function() 295 | assert_linescan([=[ 296 | local x = { + 297 | 1, ? 298 | 2 ? 299 | } ? 300 | local y = { + 301 | 3, ? 302 | id ? 303 | } ? 304 | local z = { + 305 | "" ? 306 | } ? 307 | local a = f( + 308 | true ? 309 | ) ? 310 | local b = { + 311 | id[1] ? 312 | } ? 313 | local c = { + 314 | id.abcd ? 315 | } ? 316 | local d = { + 317 | id.k1.k2 + 318 | } ? 319 | local e = { + 320 | {}, ? 321 | {} ? 322 | } ? 323 | local f = { + 324 | {foo} + 325 | } ? 326 | ]=]) 327 | 328 | assert_linescan([=[ 329 | local t1 = { + 330 | 1, ? 331 | 2} ? 332 | local t2 = f({ + 333 | 1, ? 334 | false}) ? 335 | local t3 = { + 336 | var} + 337 | ]=]) 338 | end) 339 | 340 | it("doubts table endings", function() 341 | assert_linescan([[ 342 | local v = f({ + 343 | a = "foo", ? 344 | x = y + 345 | }, ? 346 | function(g) ? 347 | g() + 348 | end) - 349 | ]]) 350 | 351 | assert_linescan([[ 352 | local v = f({ + 353 | a = "foo", ? 354 | x = y + 355 | }, function(g) ? 356 | g() + 357 | end) - 358 | ]]) 359 | end) 360 | 361 | it("supports inline 'enable' and 'disable' options", function() 362 | assert_linescan([[ 363 | outside() + 364 | -- luacov: enable - 365 | explicitly_enabled() + 366 | -- luacov: disable - 367 | disabled() - 368 | -- luacov: disable - 369 | still_disabled() - 370 | -- luacov: enable - 371 | enabled() + 372 | -- luacov: unknown - 373 | unknown_opts_ignored() + 374 | ]]) 375 | end) 376 | end) 377 | -------------------------------------------------------------------------------- /spec/nested/.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | modules = { 3 | testlib = "testlib.lua" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /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/nested/subdir/.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | savestepsize = 1, 3 | statsfile = "../luacov.stats.out" 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/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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /src/bin/luacov: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | local runner = require("luacov.runner") 3 | 4 | local patterns = {} 5 | local configfile 6 | local reporter 7 | 8 | local help_message = ([[ 9 | LuaCov %s - coverage analyzer for Lua scripts 10 | 11 | Usage: 12 | luacov [options] [pattern...] 13 | 14 | Launch your Lua programs with -lluacov to perform accounting. 15 | Launch this script to generate a report from collected stats. 16 | By default it reports on every Lua file encountered running your 17 | script. To filter filenames, pass one or more Lua patterns matching 18 | files to be included in the command line, or use a config. 19 | 20 | Options: 21 | -c filename, --config filename 22 | 23 | Use a config file, .luacov by default. For details see 24 | luacov.defaults module. 25 | 26 | -r name, --reporter name 27 | 28 | Use a custom reporter - a module in luacov.reporter.* namespace. 29 | 30 | -h, --help 31 | 32 | Show this help message and exit. 33 | 34 | Examples: 35 | luacov foo/bar 36 | 37 | This will report only on modules in the foo/bar subtree. 38 | ]]):format(runner.version) 39 | 40 | local function read_key(i) 41 | if arg[i]:sub(1, 1) ~= "-" or #arg[i] == 1 then 42 | return nil, arg[i], i + 1 43 | end 44 | 45 | if arg[i]:sub(2, 2) == "-" then 46 | local key, value = arg[i]:match("^%-%-([^=]+)=(.*)$") 47 | if key then 48 | return key, value, i + 1 49 | else 50 | return arg[i]:sub(3), arg[i + 1], i + 2 51 | end 52 | else 53 | local key = arg[i]:sub(2, 2) 54 | local value = arg[i]:sub(3) 55 | if #value == 0 then 56 | i = i + 1 57 | value = arg[i] 58 | elseif value:sub(1, 1) == "=" then 59 | value = value:sub(2) 60 | end 61 | return key, value, i + 1 62 | end 63 | end 64 | 65 | local function norm_pat(p) 66 | return (p:gsub("\\", "/"):gsub("%.lua$", "")) 67 | end 68 | 69 | local i = 1 70 | while arg[i] do 71 | local key, value 72 | key, value, i = read_key(i) 73 | if key then 74 | if (key == "h") or (key == "help") then 75 | print(help_message) 76 | os.exit(0) 77 | elseif (key == "c") or (key == "config") then 78 | configfile = value 79 | elseif (key == "r") or (key == "reporter") then 80 | reporter = value 81 | end 82 | else 83 | table.insert(patterns, norm_pat(value)) 84 | end 85 | end 86 | 87 | -- will load configfile specified, or defaults otherwise 88 | local configuration = runner.load_config(configfile) 89 | 90 | configuration.include = configuration.include or {} 91 | configuration.exclude = configuration.exclude or {} 92 | 93 | -- add elements specified on commandline to config 94 | for _, patt in ipairs(patterns) do 95 | table.insert(configuration.include, patt) 96 | end 97 | 98 | configuration.reporter = reporter or configuration.reporter 99 | runner.run_report(configuration) 100 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 9 | --- Filename to store collected stats. Default: "luacov.stats.out". 10 | statsfile = "luacov.stats.out", 11 | 12 | --- Filename to store report. Default: "luacov.report.out". 13 | reportfile = "luacov.report.out", 14 | 15 | --- Enable saving coverage data after every `savestepsize` lines? 16 | -- Setting this flag to `true` in config is equivalent to running LuaCov 17 | -- using `luacov.tick` module. Default: false. 18 | tick = false, 19 | 20 | --- Stats file updating frequency for `luacov.tick`. 21 | -- The lower this value - the more frequently results will be written out to the stats file. 22 | -- You may want to reduce this value (to, for example, 2) to avoid losing coverage data in 23 | -- case your program may terminate without triggering luacov exit hooks that are supposed 24 | -- to save the data. Default: 100. 25 | savestepsize = 100, 26 | 27 | --- Run reporter on completion? Default: false. 28 | runreport = false, 29 | 30 | --- Delete stats file after reporting? Default: false. 31 | deletestats = false, 32 | 33 | --- Process Lua code loaded from raw strings? 34 | -- That is, when the 'source' field in the debug info 35 | -- does not start with '@'. Default: false. 36 | codefromstrings = false, 37 | 38 | --- Lua patterns for files to include when reporting. 39 | -- All will be included if nothing is listed. 40 | -- Do not include the '.lua' extension. Path separator is always '/'. 41 | -- Overruled by `exclude`. 42 | -- @usage 43 | -- include = { 44 | -- "mymodule$", -- the main module 45 | -- "mymodule%/.+$", -- and everything namespaced underneath it 46 | -- } 47 | include = {}, 48 | 49 | --- Lua patterns for files to exclude when reporting. 50 | -- Nothing will be excluded if nothing is listed. 51 | -- Do not include the '.lua' extension. Path separator is always '/'. 52 | -- Overrules `include`. 53 | exclude = {}, 54 | 55 | --- Table mapping names of modules to be included to their filenames. 56 | -- Has no effect if empty. 57 | -- Real filenames mentioned here will be used for reporting 58 | -- even if the modules have been installed elsewhere. 59 | -- Module name can contain '*' wildcard to match groups of modules, 60 | -- in this case corresponding path will be used as a prefix directory 61 | -- where modules from the group are located. 62 | -- @usage 63 | -- modules = { 64 | -- ["some_rock"] = "src/some_rock.lua", 65 | -- ["some_rock.*"] = "src" 66 | -- } 67 | modules = {}, 68 | 69 | --- Enable including untested files in report. 70 | -- If `true`, all untested files in "." will be included. 71 | -- If it is a table with directory and file paths, all untested files in these paths will be included. 72 | -- Note that you are not allowed to use patterns in these paths. 73 | -- Default: false. 74 | includeuntestedfiles = false, 75 | 76 | } 77 | -------------------------------------------------------------------------------- /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 | ---------------------------------------------------------------- 8 | local dir_sep = package.config:sub(1, 1) 9 | if not dir_sep:find("[/\\]") then 10 | dir_sep = "/" 11 | end 12 | 13 | --- Creates a new debug hook. 14 | -- @param runner runner module. 15 | -- @return debug hook function that uses runner fields and functions 16 | -- and sets `runner.data`. 17 | function hook.new(runner) 18 | local ignored_files = {} 19 | local steps_after_save = 0 20 | 21 | return function(_, line_nr, level) 22 | -- Do not use string metamethods within the debug hook: 23 | -- they may be absent if it's called from a sandboxed environment 24 | -- or because of carelessly implemented monkey-patching. 25 | level = level or 2 26 | if not runner.initialized then 27 | return 28 | end 29 | 30 | -- Get name of processed file. 31 | local name = debug.getinfo(level, "S").source 32 | local prefixed_name = string.match(name, "^@(.*)") 33 | if prefixed_name then 34 | name = prefixed_name:gsub("^%.[/\\]", ""):gsub("[/\\]", dir_sep) 35 | elseif not runner.configuration.codefromstrings then 36 | -- Ignore Lua code loaded from raw strings by default. 37 | return 38 | end 39 | 40 | local data = runner.data 41 | local file = data[name] 42 | 43 | if not file then 44 | -- New or ignored file. 45 | if ignored_files[name] then 46 | return 47 | elseif runner.file_included(name) then 48 | file = {max = 0, max_hits = 0} 49 | data[name] = file 50 | else 51 | ignored_files[name] = true 52 | return 53 | end 54 | end 55 | 56 | if line_nr > file.max then 57 | file.max = line_nr 58 | end 59 | 60 | local hits = (file[line_nr] or 0) + 1 61 | file[line_nr] = hits 62 | 63 | if hits > file.max_hits then 64 | file.max_hits = hits 65 | end 66 | 67 | if runner.tick then 68 | steps_after_save = steps_after_save + 1 69 | 70 | if steps_after_save == runner.configuration.savestepsize then 71 | steps_after_save = 0 72 | 73 | if not runner.paused then 74 | runner.save_stats() 75 | end 76 | end 77 | end 78 | end 79 | end 80 | 81 | return hook 82 | -------------------------------------------------------------------------------- /src/luacov/linescanner.lua: -------------------------------------------------------------------------------- 1 | local LineScanner = {} 2 | LineScanner.__index = LineScanner 3 | 4 | function LineScanner:new() 5 | return setmetatable({ 6 | first = true, 7 | comment = false, 8 | after_function = false, 9 | enabled = true 10 | }, self) 11 | end 12 | 13 | -- Raw version of string.gsub 14 | local function replace(s, old, new) 15 | old = old:gsub("%p", "%%%0") 16 | new = new:gsub("%%", "%%%%") 17 | return (s:gsub(old, new)) 18 | end 19 | 20 | local fixups = { 21 | { "=", " ?= ?" }, -- '=' may be surrounded by spaces 22 | { "(", " ?%( ?" }, -- '(' may be surrounded by spaces 23 | { ")", " ?%) ?" }, -- ')' may be surrounded by spaces 24 | { "", "x ?[%[%.]? ?[ntfx0']* ?%]?" }, -- identifier, possibly indexed once 25 | { "", "x ?, ?x[x, ]*" }, -- at least two comma-separated identifiers 26 | { "", "%[? ?[ntfx0']+ ?%]?" }, -- field, possibly like ["this"] 27 | { "", "[ %(]*" }, -- optional opening parentheses 28 | } 29 | 30 | -- Utility function to make patterns more readable 31 | local function fixup(pat) 32 | for _, fixup_pair in ipairs(fixups) do 33 | pat = replace(pat, fixup_pair[1], fixup_pair[2]) 34 | end 35 | 36 | return pat 37 | end 38 | 39 | --- Lines that are always excluded from accounting 40 | local any_hits_exclusions = { 41 | "", -- Empty line 42 | "end[,; %)]*", -- Single "end" 43 | "else", -- Single "else" 44 | "repeat", -- Single "repeat" 45 | "do", -- Single "do" 46 | "if", -- Single "if" 47 | "then", -- Single "then" 48 | "while t do", -- "while true do" generates no code 49 | "if t then", -- "if true then" generates no code 50 | "local x", -- "local var" 51 | fixup "local x=", -- "local var =" 52 | fixup "local ", -- "local var1, ..., varN" 53 | fixup "local =", -- "local var1, ..., varN =" 54 | "local function x", -- "local function f (arg1, ..., argN)" 55 | } 56 | 57 | --- Lines that are only excluded from accounting when they have 0 hits 58 | local zero_hits_exclusions = { 59 | "[ntfx0',= ]+,", -- "var1 var2," multi columns table stuff 60 | "{ ?} ?,", -- Empty table before comma leaves no trace in tables and calls 61 | fixup "=.+[,;]", -- "[123] = 23," "['foo'] = "asd"," 62 | fixup "=function", -- "[123] = function(...)" 63 | fixup "='", -- "[123] = [[", possibly with opening parens 64 | "return function", -- "return function(arg1, ..., argN)" 65 | "function", -- "function(arg1, ..., argN)" 66 | "[ntfx0]", -- Single token expressions leave no trace in tables, function calls and sometimes assignments 67 | "''", -- Same for strings 68 | "{ ?}", -- Same for empty tables 69 | fixup "", -- Same for local variables indexed once 70 | fixup "local x=function", -- "local a = function(arg1, ..., argN)" 71 | fixup "local x='", -- "local a = [[", possibly with opening parens 72 | fixup "local x=(", -- "local a = (", possibly with several parens 73 | fixup "local =(", -- "local a, b = (", possibly with several parens 74 | fixup "local x=n", -- "local a = nil; local b = nil" produces no trace for the second statement 75 | fixup "='", -- "a.b = [[", possibly with opening parens 76 | fixup "=function", -- "a = function(arg1, ..., argN)" 77 | "} ?,", -- "}," generates no trace if the table ends with a key-value pair 78 | "} ?, ?function", -- same with "}, function(...)" 79 | "break", -- "break" generates no trace in Lua 5.2+ 80 | "{", -- "{" opening table 81 | "}?[ %)]*", -- optional closing paren, possibly with several closing parens 82 | "[ntf0']+ ?}[ %)]*", -- a constant at the end of a table, possibly with closing parens (for LuaJIT) 83 | "goto [%w_]+", -- goto statements 84 | "::[%w_]+::", -- labels 85 | } 86 | 87 | local function excluded(exclusions, line) 88 | for _, e in ipairs(exclusions) do 89 | if line:match("^ *"..e.." *$") then 90 | return true 91 | end 92 | end 93 | 94 | return false 95 | end 96 | 97 | function LineScanner:find(pattern) 98 | return self.line:find(pattern, self.i) 99 | end 100 | 101 | -- Skips string literal with quote stored as self.quote. 102 | -- @return boolean indicating success. 103 | function LineScanner:skip_string() 104 | -- Look for closing quote, possibly after even number of backslashes. 105 | local _, quote_i = self:find("^(\\*)%1"..self.quote) 106 | if not quote_i then 107 | _, quote_i = self:find("[^\\](\\*)%1"..self.quote) 108 | end 109 | 110 | if quote_i then 111 | self.i = quote_i + 1 112 | self.quote = nil 113 | table.insert(self.simple_line_buffer, "'") 114 | return true 115 | else 116 | return false 117 | end 118 | end 119 | 120 | -- Skips long string literal with equal signs stored as self.equals. 121 | -- @return boolean indicating success. 122 | function LineScanner:skip_long_string() 123 | local _, bracket_i = self:find("%]"..self.equals.."%]") 124 | 125 | if bracket_i then 126 | self.i = bracket_i + 1 127 | self.equals = nil 128 | 129 | if self.comment then 130 | self.comment = false 131 | else 132 | table.insert(self.simple_line_buffer, "'") 133 | end 134 | 135 | return true 136 | else 137 | return false 138 | end 139 | end 140 | 141 | -- Skips function arguments. 142 | -- @return boolean indicating success. 143 | function LineScanner:skip_args() 144 | local _, paren_i = self:find("%)") 145 | 146 | if paren_i then 147 | self.i = paren_i + 1 148 | self.args = nil 149 | return true 150 | else 151 | return false 152 | end 153 | end 154 | 155 | function LineScanner:skip_whitespace() 156 | local next_i = self:find("%S") or #self.line + 1 157 | 158 | if next_i ~= self.i then 159 | self.i = next_i 160 | table.insert(self.simple_line_buffer, " ") 161 | end 162 | end 163 | 164 | function LineScanner:skip_number() 165 | if self:find("^0[xX]") then 166 | self.i = self.i + 2 167 | end 168 | 169 | local _ 170 | _, _, self.i = self:find("^[%x%.]*()") 171 | 172 | if self:find("^[eEpP][%+%-]") then 173 | -- Skip exponent, too. 174 | self.i = self.i + 2 175 | _, _, self.i = self:find("^[%x%.]*()") 176 | end 177 | 178 | -- Skip LuaJIT number suffixes (i, ll, ull). 179 | _, _, self.i = self:find("^[iull]*()") 180 | table.insert(self.simple_line_buffer, "0") 181 | end 182 | 183 | local keywords = {["nil"] = "n", ["true"] = "t", ["false"] = "f"} 184 | 185 | for _, keyword in ipairs({ 186 | "and", "break", "do", "else", "elseif", "end", "for", "function", "goto", "if", 187 | "in", "local", "not", "or", "repeat", "return", "then", "until", "while"}) do 188 | keywords[keyword] = keyword 189 | end 190 | 191 | function LineScanner:skip_name() 192 | -- It is guaranteed that the first character matches "%a_". 193 | local _, _, name = self:find("^([%w_]*)") 194 | self.i = self.i + #name 195 | 196 | if keywords[name] then 197 | name = keywords[name] 198 | else 199 | name = "x" 200 | end 201 | 202 | table.insert(self.simple_line_buffer, name) 203 | 204 | if name == "function" then 205 | -- This flag indicates that the next pair of parentheses (function args) must be skipped. 206 | self.after_function = true 207 | end 208 | end 209 | 210 | -- Source lines can be explicitly ignored using `enable` and `disable` inline options. 211 | -- An inline option is a simple comment: `-- luacov: enable` or `-- luacov: disable`. 212 | -- Inline option parsing is not whitespace sensitive. 213 | -- All lines starting from a line containing `disable` option and up to a line containing `enable` 214 | -- option (or end of file) are excluded. 215 | 216 | function LineScanner:check_inline_options(comment_body) 217 | if comment_body:find("^%s*luacov:%s*enable%s*$") then 218 | self.enabled = true 219 | elseif comment_body:find("^%s*luacov:%s*disable%s*$") then 220 | self.enabled = false 221 | end 222 | end 223 | 224 | -- Consumes and analyzes a line. 225 | -- @return boolean indicating whether line must be excluded. 226 | -- @return boolean indicating whether line must be excluded if not hit. 227 | function LineScanner:consume(line) 228 | if self.first then 229 | self.first = false 230 | 231 | if line:match("^#!") then 232 | -- Ignore Unix hash-bang magic line. 233 | return true, true 234 | end 235 | end 236 | 237 | self.line = line 238 | -- As scanner goes through the line, it puts its simplified parts into buffer. 239 | -- Punctuation is preserved. Whitespace is replaced with single space. 240 | -- Literal strings are replaced with "''", so that a string literal 241 | -- containing special characters does not confuse exclusion rules. 242 | -- Numbers are replaced with "0". 243 | -- Identifiers are replaced with "x". 244 | -- Literal keywords (nil, true and false) are replaced with "n", "t" and "f", 245 | -- other keywords are preserved. 246 | -- Function declaration arguments are removed. 247 | self.simple_line_buffer = {} 248 | self.i = 1 249 | 250 | while self.i <= #line do 251 | -- One iteration of this loop handles one token, where 252 | -- string literal start and end are considered distinct tokens. 253 | if self.quote then 254 | if not self:skip_string() then 255 | -- String literal ends on another line. 256 | break 257 | end 258 | elseif self.equals then 259 | if not self:skip_long_string() then 260 | -- Long string literal or comment ends on another line. 261 | break 262 | end 263 | elseif self.args then 264 | if not self:skip_args() then 265 | -- Function arguments end on another line. 266 | break 267 | end 268 | else 269 | self:skip_whitespace() 270 | 271 | if self:find("^%.%d") then 272 | self.i = self.i + 1 273 | end 274 | 275 | if self:find("^%d") then 276 | self:skip_number() 277 | elseif self:find("^[%a_]") then 278 | self:skip_name() 279 | else 280 | if self:find("^%-%-") then 281 | self.comment = true 282 | self.i = self.i + 2 283 | end 284 | 285 | local _, bracket_i, equals = self:find("^%[(=*)%[") 286 | if equals then 287 | self.i = bracket_i + 1 288 | self.equals = equals 289 | 290 | if not self.comment then 291 | table.insert(self.simple_line_buffer, "'") 292 | end 293 | elseif self.comment then 294 | -- Simple comment, check if it contains inline options and skip line. 295 | self.comment = false 296 | local comment_body = self.line:sub(self.i) 297 | self:check_inline_options(comment_body) 298 | break 299 | else 300 | local char = line:sub(self.i, self.i) 301 | 302 | if char == "." then 303 | -- Dot can't be saved as one character because of 304 | -- ".." and "..." tokens and the fact that number literals 305 | -- can start with one. 306 | local _, _, dots = self:find("^(%.*)") 307 | self.i = self.i + #dots 308 | table.insert(self.simple_line_buffer, dots) 309 | else 310 | self.i = self.i + 1 311 | 312 | if char == "'" or char == '"' then 313 | table.insert(self.simple_line_buffer, "'") 314 | self.quote = char 315 | elseif self.after_function and char == "(" then 316 | -- This is the opening parenthesis of function declaration args. 317 | self.after_function = false 318 | self.args = true 319 | else 320 | -- Save other punctuation literally. 321 | -- This inserts an empty string when at the end of line, 322 | -- which is fine. 323 | table.insert(self.simple_line_buffer, char) 324 | end 325 | end 326 | end 327 | end 328 | end 329 | end 330 | 331 | if not self.enabled then 332 | -- Disabled by inline options, always exclude the line. 333 | return true, true 334 | end 335 | 336 | local simple_line = table.concat(self.simple_line_buffer) 337 | return excluded(any_hits_exclusions, simple_line), excluded(zero_hits_exclusions, simple_line) 338 | end 339 | 340 | return LineScanner 341 | -------------------------------------------------------------------------------- /src/luacov/reporter.lua: -------------------------------------------------------------------------------- 1 | ------------------------ 2 | -- Report module, will transform statistics file into a report. 3 | -- @class module 4 | -- @name luacov.reporter 5 | local reporter = {} 6 | 7 | local LineScanner = require("luacov.linescanner") 8 | local luacov = require("luacov.runner") 9 | local util = require("luacov.util") 10 | local lfs_ok, lfs = pcall(require, "lfs") 11 | 12 | ---------------------------------------------------------------- 13 | local dir_sep = package.config:sub(1, 1) 14 | if not dir_sep:find("[/\\]") then 15 | dir_sep = "/" 16 | end 17 | 18 | 19 | --- returns all files inside dir 20 | --- @param dir directory to be listed 21 | --- @treturn table with filenames and attributes 22 | local function dirtree(dir) 23 | assert(dir and dir ~= "", "Please pass directory parameter") 24 | if dir:sub(-1):match("[/\\]") then 25 | dir=string.sub(dir, 1, -2) 26 | end 27 | 28 | dir = dir:gsub("[/\\]", dir_sep) 29 | 30 | local function yieldtree(directory) 31 | for entry in lfs.dir(directory) do 32 | if entry ~= "." and entry ~= ".." then 33 | entry=directory..dir_sep..entry 34 | local attr=lfs.attributes(entry) 35 | coroutine.yield(entry,attr) 36 | if attr.mode == "directory" then 37 | yieldtree(entry) 38 | end 39 | end 40 | end 41 | end 42 | 43 | return coroutine.wrap(function() yieldtree(dir) end) 44 | end 45 | 46 | ---------------------------------------------------------------- 47 | --- checks if string 'filename' has pattern 'pattern' 48 | --- @param filename 49 | --- @param pattern 50 | --- @return boolean 51 | local function fileMatches(filename, pattern) 52 | return string.find(filename, pattern) 53 | end 54 | 55 | ---------------------------------------------------------------- 56 | --- Basic reporter class stub. 57 | -- Implements 'new', 'run' and 'close' methods required by `report`. 58 | -- Provides some helper methods and stubs to be overridden by child classes. 59 | -- @usage 60 | -- local MyReporter = setmetatable({}, ReporterBase) 61 | -- MyReporter.__index = MyReporter 62 | -- function MyReporter:on_hit_line(...) 63 | -- self:write(("File %s: hit line %s %d times"):format(...)) 64 | -- end 65 | -- @type ReporterBase 66 | local ReporterBase = {} do 67 | ReporterBase.__index = ReporterBase 68 | 69 | function ReporterBase:new(conf) 70 | local stats = require("luacov.stats") 71 | local data = stats.load(conf.statsfile) 72 | 73 | if not data then 74 | return nil, "Could not load stats file " .. conf.statsfile .. "." 75 | end 76 | 77 | local files = {} 78 | local filtered_data = {} 79 | local max_hits = 0 80 | 81 | -- Several original paths can map to one real path, 82 | -- their stats should be merged in this case. 83 | for filename, file_stats in pairs(data) do 84 | if luacov.file_included(filename) then 85 | filename = luacov.real_name(filename) 86 | 87 | if filtered_data[filename] then 88 | luacov.update_stats(filtered_data[filename], file_stats) 89 | else 90 | table.insert(files, filename) 91 | filtered_data[filename] = file_stats 92 | end 93 | 94 | max_hits = math.max(max_hits, filtered_data[filename].max_hits) 95 | end 96 | end 97 | 98 | -- including files without tests 99 | -- only .lua files 100 | if conf.includeuntestedfiles then 101 | if not lfs_ok then 102 | print("The option includeuntestedfiles requires the lfs module (from luafilesystem) to be installed.") 103 | os.exit(1) 104 | end 105 | 106 | local function add_empty_file_coverage_data(file_path) 107 | 108 | -- Leading "./" must be trimmed from the file paths because the paths of tested 109 | -- files do not have a leading "./" either 110 | if (file_path:match("^%.[/\\]")) then 111 | file_path = file_path:sub(3) 112 | end 113 | 114 | if luacov.file_included(file_path) then 115 | local file_stats = { 116 | max = 0, 117 | max_hits = 0 118 | } 119 | 120 | local filename = luacov.real_name(file_path) 121 | 122 | if not filtered_data[filename] then 123 | table.insert(files, filename) 124 | filtered_data[filename] = file_stats 125 | end 126 | end 127 | 128 | end 129 | 130 | local function add_empty_dir_coverage_data(directory_path) 131 | 132 | for filename, attr in dirtree(directory_path) do 133 | if attr.mode == "file" and fileMatches(filename, '.%.lua$') then 134 | add_empty_file_coverage_data(filename) 135 | end 136 | end 137 | 138 | end 139 | 140 | if (conf.includeuntestedfiles == true) then 141 | add_empty_dir_coverage_data("." .. dir_sep) 142 | 143 | elseif (type(conf.includeuntestedfiles) == "table" and conf.includeuntestedfiles[1]) then 144 | for _, include_path in ipairs(conf.includeuntestedfiles) do 145 | if (fileMatches(include_path, '.%.lua$')) then 146 | add_empty_file_coverage_data(include_path) 147 | else 148 | add_empty_dir_coverage_data(include_path) 149 | end 150 | end 151 | end 152 | 153 | end 154 | 155 | table.sort(files) 156 | 157 | local out, err = io.open(conf.reportfile, "w") 158 | if not out then return nil, err end 159 | 160 | local o = setmetatable({ 161 | _out = out, 162 | _cfg = conf, 163 | _data = filtered_data, 164 | _files = files, 165 | _mhit = max_hits, 166 | }, self) 167 | 168 | return o 169 | end 170 | 171 | --- Returns configuration table. 172 | -- @see luacov.defaults 173 | function ReporterBase:config() 174 | return self._cfg 175 | end 176 | 177 | --- Returns maximum number of hits per line in all coverage data. 178 | function ReporterBase:max_hits() 179 | return self._mhit 180 | end 181 | 182 | --- Writes strings to report file. 183 | -- @param ... strings. 184 | function ReporterBase:write(...) 185 | return self._out:write(...) 186 | end 187 | 188 | function ReporterBase:close() 189 | self._out:close() 190 | self._private = nil 191 | end 192 | 193 | --- Returns array of filenames to be reported. 194 | function ReporterBase:files() 195 | return self._files 196 | end 197 | 198 | --- Returns coverage data for a file. 199 | -- @param filename name of the file. 200 | -- @see luacov.stats.load 201 | function ReporterBase:stats(filename) 202 | return self._data[filename] 203 | end 204 | 205 | -- Stub methods follow. 206 | -- luacheck: push no unused args 207 | 208 | --- Stub method called before reporting. 209 | function ReporterBase:on_start() 210 | end 211 | 212 | --- Stub method called before processing a file. 213 | -- @param filename name of the file. 214 | function ReporterBase:on_new_file(filename) 215 | end 216 | 217 | --- Stub method called if a file couldn't be processed due to an error. 218 | -- @param filename name of the file. 219 | -- @param error_type "open", "read" or "load". 220 | -- @param message error message. 221 | function ReporterBase:on_file_error(filename, error_type, message) 222 | end 223 | 224 | --- Stub method called for each empty source line 225 | -- and other lines that can't be hit. 226 | -- @param filename name of the file. 227 | -- @param lineno line number. 228 | -- @param line the line itself as a string. 229 | function ReporterBase:on_empty_line(filename, lineno, line) 230 | end 231 | 232 | --- Stub method called for each missed source line. 233 | -- @param filename name of the file. 234 | -- @param lineno line number. 235 | -- @param line the line itself as a string. 236 | function ReporterBase:on_mis_line(filename, lineno, line) 237 | end 238 | 239 | --- Stub method called for each hit source line. 240 | -- @param filename name of the file. 241 | -- @param lineno line number. 242 | -- @param line the line itself as a string. 243 | -- @param hits number of times the line was hit. Should be positive. 244 | function ReporterBase:on_hit_line(filename, lineno, line, hits) 245 | end 246 | 247 | --- Stub method called after a file has been processed. 248 | -- @param filename name of the file. 249 | -- @param hits total number of hit lines in the file. 250 | -- @param miss total number of missed lines in the file. 251 | function ReporterBase:on_end_file(filename, hits, miss) 252 | end 253 | 254 | --- Stub method called after reporting. 255 | function ReporterBase:on_end() 256 | end 257 | 258 | -- luacheck: pop 259 | 260 | local cluacov_ok = pcall(require, "cluacov.version") 261 | local deepactivelines 262 | 263 | if cluacov_ok then 264 | deepactivelines = require("cluacov.deepactivelines") 265 | end 266 | 267 | function ReporterBase:_run_file(filename) 268 | local file, open_err = io.open(filename) 269 | 270 | if not file then 271 | self:on_file_error(filename, "open", util.unprefix(open_err, filename .. ": ")) 272 | return 273 | end 274 | 275 | local active_lines 276 | 277 | if cluacov_ok then 278 | local src, read_err = file:read("*a") 279 | 280 | if not src then 281 | self:on_file_error(filename, "read", read_err) 282 | return 283 | end 284 | 285 | src = src:gsub("^#![^\n]*", "") 286 | local func, load_err = util.load_string(src, nil, "@file") 287 | 288 | if not func then 289 | self:on_file_error(filename, "load", "line " .. util.unprefix(load_err, "file:")) 290 | return 291 | end 292 | 293 | active_lines = deepactivelines.get(func) 294 | file:seek("set") 295 | end 296 | 297 | self:on_new_file(filename) 298 | local file_hits, file_miss = 0, 0 299 | local filedata = self:stats(filename) 300 | 301 | local line_nr = 1 302 | local scanner = LineScanner:new() 303 | 304 | while true do 305 | local line = file:read("*l") 306 | if not line then break end 307 | 308 | local always_excluded, excluded_when_not_hit = scanner:consume(line) 309 | local hits = filedata[line_nr] or 0 310 | local included = not always_excluded and (not excluded_when_not_hit or hits ~= 0) 311 | 312 | if cluacov_ok then 313 | included = included and active_lines[line_nr] 314 | end 315 | 316 | if included then 317 | if hits == 0 then 318 | self:on_mis_line(filename, line_nr, line) 319 | file_miss = file_miss + 1 320 | else 321 | self:on_hit_line(filename, line_nr, line, hits) 322 | file_hits = file_hits + 1 323 | end 324 | else 325 | self:on_empty_line(filename, line_nr, line) 326 | end 327 | 328 | line_nr = line_nr + 1 329 | end 330 | 331 | file:close() 332 | self:on_end_file(filename, file_hits, file_miss) 333 | end 334 | 335 | function ReporterBase:run() 336 | self:on_start() 337 | 338 | for _, filename in ipairs(self:files()) do 339 | self:_run_file(filename) 340 | end 341 | 342 | self:on_end() 343 | end 344 | 345 | end 346 | --- @section end 347 | ---------------------------------------------------------------- 348 | 349 | ---------------------------------------------------------------- 350 | local DefaultReporter = setmetatable({}, ReporterBase) do 351 | DefaultReporter.__index = DefaultReporter 352 | 353 | function DefaultReporter:on_start() 354 | local most_hits = self:max_hits() 355 | local most_hits_length = #("%d"):format(most_hits) 356 | 357 | self._summary = {} 358 | self._empty_format = (" "):rep(most_hits_length + 1) 359 | self._zero_format = ("*"):rep(most_hits_length).."0" 360 | self._count_format = ("%% %dd"):format(most_hits_length+1) 361 | self._printed_first_header = false 362 | end 363 | 364 | function DefaultReporter:on_new_file(filename) 365 | self:write(("="):rep(78), "\n") 366 | self:write(filename, "\n") 367 | self:write(("="):rep(78), "\n") 368 | end 369 | 370 | function DefaultReporter:on_file_error(filename, error_type, message) --luacheck: no self 371 | io.stderr:write(("Couldn't %s %s: %s\n"):format(error_type, filename, message)) 372 | end 373 | 374 | function DefaultReporter:on_empty_line(_, _, line) 375 | if line == "" then 376 | self:write("\n") 377 | else 378 | self:write(self._empty_format, " ", line, "\n") 379 | end 380 | end 381 | 382 | function DefaultReporter:on_mis_line(_, _, line) 383 | self:write(self._zero_format, " ", line, "\n") 384 | end 385 | 386 | function DefaultReporter:on_hit_line(_, _, line, hits) 387 | self:write(self._count_format:format(hits), " ", line, "\n") 388 | end 389 | 390 | function DefaultReporter:on_end_file(filename, hits, miss) 391 | self._summary[filename] = { hits = hits, miss = miss } 392 | self:write("\n") 393 | end 394 | 395 | local function coverage_to_string(hits, missed) 396 | local total = hits + missed 397 | 398 | if total == 0 then 399 | total = 1 400 | end 401 | 402 | return ("%.2f%%"):format(hits/total*100.0) 403 | end 404 | 405 | function DefaultReporter:on_end() 406 | self:write(("="):rep(78), "\n") 407 | self:write("Summary\n") 408 | self:write(("="):rep(78), "\n") 409 | self:write("\n") 410 | 411 | local lines = {{"File", "Hits", "Missed", "Coverage"}} 412 | local total_hits, total_missed = 0, 0 413 | 414 | for _, filename in ipairs(self:files()) do 415 | local summary = self._summary[filename] 416 | 417 | if summary then 418 | local hits, missed = summary.hits, summary.miss 419 | 420 | table.insert(lines, { 421 | filename, 422 | tostring(summary.hits), 423 | tostring(summary.miss), 424 | coverage_to_string(hits, missed) 425 | }) 426 | 427 | total_hits = total_hits + hits 428 | total_missed = total_missed + missed 429 | end 430 | end 431 | 432 | table.insert(lines, { 433 | "Total", 434 | tostring(total_hits), 435 | tostring(total_missed), 436 | coverage_to_string(total_hits, total_missed) 437 | }) 438 | 439 | local max_column_lengths = {} 440 | 441 | for _, line in ipairs(lines) do 442 | for column_nr, column in ipairs(line) do 443 | max_column_lengths[column_nr] = math.max(max_column_lengths[column_nr] or -1, #column) 444 | end 445 | end 446 | 447 | local table_width = #max_column_lengths - 1 448 | 449 | for _, column_length in ipairs(max_column_lengths) do 450 | table_width = table_width + column_length 451 | end 452 | 453 | 454 | for line_nr, line in ipairs(lines) do 455 | if line_nr == #lines or line_nr == 2 then 456 | self:write(("-"):rep(table_width), "\n") 457 | end 458 | 459 | for column_nr, column in ipairs(line) do 460 | self:write(column) 461 | 462 | if column_nr == #line then 463 | self:write("\n") 464 | else 465 | self:write((" "):rep(max_column_lengths[column_nr] - #column + 1)) 466 | end 467 | end 468 | end 469 | end 470 | 471 | end 472 | ---------------------------------------------------------------- 473 | 474 | --- Runs the report generator. 475 | -- To load a config, use `luacov.runner.load_config` first. 476 | -- @param[opt] reporter_class custom reporter class. Will be 477 | -- instantiated using 'new' method with configuration 478 | -- (see `luacov.defaults`) as the argument. It should 479 | -- return nil + error if something went wrong. 480 | -- After acquiring a reporter object its 'run' and 'close' 481 | -- methods will be called. 482 | -- The easiest way to implement a custom reporter class is to 483 | -- extend `ReporterBase`. 484 | function reporter.report(reporter_class) 485 | local configuration = luacov.load_config() 486 | 487 | reporter_class = reporter_class or DefaultReporter 488 | 489 | local rep, err = reporter_class:new(configuration) 490 | 491 | if not rep then 492 | print(err) 493 | print("Run your Lua program with -lluacov and then rerun luacov.") 494 | os.exit(1) 495 | end 496 | 497 | rep:run() 498 | 499 | rep:close() 500 | 501 | if configuration.deletestats then 502 | os.remove(configuration.statsfile) 503 | end 504 | end 505 | 506 | reporter.ReporterBase = ReporterBase 507 | 508 | reporter.DefaultReporter = DefaultReporter 509 | 510 | return reporter 511 | -------------------------------------------------------------------------------- /src/luacov/reporter/default.lua: -------------------------------------------------------------------------------- 1 | return require "luacov.reporter" -------------------------------------------------------------------------------- /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