-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathcoco.nim
167 lines (138 loc) · 6.38 KB
/
coco.nim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
## This module is the API behind the binary Coco.
##
## Coco makes line or branch code coverage for Nim a breeze.
## - Depends on LCOV
## - Generates a nice looking HTML report
## - Works with visualization tools like Coverage Gutters on VSCode.
import glob, strutils, os, strformat, sequtils
const nimcache_default = "nimcache"
const fileinfo = "lcov.info"
const report_path = "coverage"
# Workaround for #12376 (https://github.com/nim-lang/Nim/issues/12376)
const generated_not_to_break_here = "generated_not_to_break_here"
proc exec(command: string) =
## Wrapper around execShellCmd that exits if the command fail
doAssert execShellCmd(command) == 0, "command failed: " & command
proc get_cache_folder*(filename, nimcache: string, increment=0): string =
fmt"{nimcache}/{filename}_{increment}_cov"
proc compile*(target="tests/**/*.nim", nimcache=nimcache_default, verbose=false, options= "") =
## Compiles Nim files in coverage mode
## - target should be a Glob with a .nim extension
var nim_args = "--hints:off"
if verbose:
nim_args = ""
if options.len > 0:
nim_args &= fmt" {options}"
var i = 0 # used to avoid name folder conflicts
for nimfile in walkGlob(target):
var cache_folder =
nimfile
.extractFilename()
.get_cache_folder(nimcache, i)
exec(fmt"nim {nim_args} --nimcache={cache_folder} --debugger:native --passC:--coverage --passL:--coverage c " & nimfile)
i.inc()
proc reset_coverage*(source=fileinfo, path=report_path, nimcache=nimcache_default) =
## Removes everything generated by a past code coverage generation:
## - Nimcache folder
## - .info
## - Code coverage report folder
try:
removeDir(nimcache)
removeDir(path)
removeFile(source)
except OSError:
echo "No past code coverage to clean up."
proc trace*(target: string) =
## Runs the compiled Nim files to produce coverage informations.
## - target should be a Glob with a .nim extension.
for nimfile in walkGlob(target):
var bin = nimfile
removeSuffix(bin, ".nim")
exec(fmt"./{bin}")
# cleanup after execution
removeFile(bin)
proc build_lcov_args(verbose=false, branch=false): string =
## Simple LCOV arguments wrapper
var lcov_args = "--quiet"
if verbose:
lcov_args = ""
if branch:
lcov_args &= " --rc lcov_branch_coverage=1"
lcov_args
proc cleanup_report*(fileinfo = fileinfo, cov: string, verbose=false, branch=false) =
## Keeps only relevant coverage informations
## Without any cleanup, your code coverage report will include standard libraries, tests and so on.
let lcov_args = build_lcov_args(verbose, branch)
const options: GlobOptions = {GlobOption.Directories, GlobOption.Absolute}
# Remove standard lib and nimble pkgs informations
let currentFolder = absolutePath("")
exec(fmt"""lcov {lcov_args} --extract {fileinfo} "{currentFolder}*" -o {fileinfo}""")
exec(fmt"""lcov {lcov_args} --remove {fileinfo} "{currentFolder}/{generated_not_to_break_here}" -o {fileinfo}""")
for pattern in cov.split(","):
if pattern.startsWith("!"):
var pattern_noprefix = pattern
removePrefix(pattern_noprefix, "!")
if existsFile(pattern_noprefix):
exec(fmt"""lcov {lcov_args} --remove {fileinfo} "{currentFolder}/{pattern_noprefix}" -o {fileinfo}""")
for path in walkGlob(pattern_noprefix, "", options):
exec(fmt"""lcov {lcov_args} --remove {fileinfo} "{path}*" -o {fileinfo}""")
else:
for path in walkGlob(pattern, "", options):
exec(fmt"""lcov {lcov_args} --extract {fileinfo} "{path}*" -o {fileinfo}""")
proc genhtml*(source=fileinfo, path=report_path, verbose=false, branch=false) =
## Generates the HTML code coverage report from a .info file generated by LCOV
var lcov_args = "--quiet"
if verbose:
lcov_args = ""
if branch:
lcov_args &= " --branch-coverage"
## Generate LCOV Html report
exec(fmt"genhtml {lcov_args} --legend -o {path} {source}")
proc coverage*(target="tests/**/*.nim", cov="!tests", verbose=false, branch=false, nimcache=nimcache_default, report_source=fileinfo, report_path=report_path, compiler="", gen_html=true): int =
## ____
##
## Code coverage for Nim:
## 1. Clean up past reports
## 2. Compile nim files in coverage mode
## 3. Run the the executables
## 4. Capture, produce and cleanup LCOV .info file
## 5. Generate the HTML report
##
reset_coverage(report_source, report_path, nimcache)
let targets = target.split(",")
for target in targets:
compile(target, nimcache, verbose, compiler)
let lcov_args = build_lcov_args(verbose, branch)
writeFile(generated_not_to_break_here, "")
exec(fmt"lcov {lcov_args} --base-directory . --directory {nimcache} --zerocounters -q")
echo "Running tests..."
for target in targets:
trace(target)
echo "Preparing coverage report..."
exec(fmt"lcov {lcov_args} --base-directory . --directory {nimcache} -c -o {report_source}")
removeFile(generated_not_to_break_here)
echo "Cleaning up report..."
cleanup_report(report_source, cov, verbose, branch)
if gen_html:
echo "Generating html..."
genhtml(report_source, report_path, verbose, branch)
result = 0
when isMainModule:
import cligen
dispatch(coverage,
help = {
"target": "Nim files to compile and run in coverage mode. Direct path or glob patterns.",
"cov": "Path to folders and files you want in your code coverage report. Default takes your current directory and excludes tests/ folder. Support glob patterns. ",
"verbose": "Displays all traces coming from LCOV and Nim",
"branch": "Enables LCOV branch code coverage mode",
"nimcache": "Nimcache path used by the Nim compiler",
"report_source": "Path used by LCOV to generate the file .info",
"report_path": "Folder path where the HTML code coverage report will be created",
"compiler": "Forward your parameter(s) to the Nim compiler",
"gen_html": "Create a html readable version of the code coverage"
},
short = {
"report_source": 's',
"report_path": 'p'
}
)