60 |
61 | Line data Source code
62 |
63 | 1 : ## This module is the API behind the binary Coco.
64 | 2 : ##
65 | 3 : ## Coco makes line or branch code coverage for Nim a breeze.
66 | 4 : ## - Depends on LCOV
67 | 5 : ## - Generates a nice looking HTML report
68 | 6 : ## - Works with visualization tools like Coverage Gutters on VSCode.
69 | 7 :
70 | 8 : import glob, strutils, os, strformat, sequtils
71 | 9 :
72 | 10 : proc is_successful(ret: int): bool =
73 | 11 22 : if ret == 0:
74 | 12 22 : true
75 | 13 22 : else:
76 | 14 : false
77 | 15 0 :
78 | 16 : proc exit_on_fail(success: bool): void {.discardable.} =
79 | 17 66 : if not success:
80 | 18 0 : quit("Command line failed. Coco exited.")
81 | 19 : proc exec(command: string): void {.discardable.} =
82 | 20 22 : ## Wrapper around execShellCmd that exits if the command fail
83 | 21 44 : execShellCmd(command)
84 | 22 44 : .is_successful()
85 | 23 22 : .exit_on_fail()
86 | 24 :
87 | 25 : proc get_cache_folder*(filename, nimcache: string, increment=0): string =
88 | 26 52 : &"{nimcache}/{filename}_{increment}_cov"
89 | 27 :
90 | 28 : proc compile*(target="tests/**/*.nim", nimcache="nimcache", verbose=false, options= "") =
91 | 29 6 : ## Compiles Nim files in coverage mode
92 | 30 : ## - target should be a Glob with a .nim extension
93 | 31 5 : var nim_args = "--hints:off"
94 | 32 7 : if verbose:
95 | 33 1 : nim_args = ""
96 | 34 16 : if options.len > 0:
97 | 35 2 : nim_args &= &" {options}"
98 | 36 :
99 | 37 4 : var i = 0 # used to avoid name folder conflicts
100 | 38 : for nimfile in walkGlob(target):
101 | 39 4 : var cache_folder =
102 | 40 : nimfile
103 | 41 4 : .extractFilename()
104 | 42 : .get_cache_folder(nimcache, i)
105 | 43 :
106 | 44 28 : exec(&"nim {nim_args} --nimcache={cache_folder} --debugger:native --passC:--coverage --passL:--coverage c " & nimfile)
107 | 45 4 : i.inc()
108 | 46 :
109 | 47 4 : proc reset_coverage*(source="lcov.info", path="coverage", nimcache="nimcache") =
110 | 48 3 : ## Removes everything generated by a past code coverage generation:
111 | 49 : ## - Nimcache folder
112 | 50 4 : ## - .info
113 | 51 : ## - Code coverage report folder
114 | 52 17 : exec(&"rm -rf {source} {path} {nimcache}")
115 | 53 :
116 | 54 4 : proc trace*(target: string) =
117 | 55 3 : ## Runs the compiled Nim files to produce coverage informations.
118 | 56 0 : ## - target should be a Glob with a .nim extension.
119 | 57 :
120 | 58 : for nimfile in walkGlob(target):
121 | 59 3 : var bin = nimfile
122 | 60 3 : removeSuffix(bin, ".nim")
123 | 61 9 : exec("./" & bin)
124 | 62 3 :
125 | 63 : proc build_lcov_args(verbose=false, branch=false): string =
126 | 64 7 : ## Simple LCOV arguments wrapper
127 | 65 4 : var lcov_args = "--quiet"
128 | 66 6 : if verbose:
129 | 67 5 : lcov_args = ""
130 | 68 6 : if branch:
131 | 69 8 : lcov_args &= " --rc lcov_branch_coverage=1"
132 | 70 : lcov_args
133 | 71 7 :
134 | 72 0 : proc cleanup_report*(fileinfo = "lcov.info", cov: string, verbose=false, branch=false) =
135 | 73 2 : ## Keeps only relevant coverage informations
136 | 74 : ## Without any cleanup, your code coverage report will include standard libraries, tests and so on.
137 | 75 :
138 | 76 2 : var lcov_args = build_lcov_args(verbose, branch)
139 | 77 2 : var options: GlobOptions = {GlobOption.Directories, GlobOption.Absolute}
140 | 78 :
141 | 79 : # Remove standard lib and nimble pkgs informations
142 | 80 2 : var currentFolder = absolutePath("")
143 | 81 8 : exec(&"""lcov {lcov_args} --extract {fileinfo} "{currentFolder}*" -o {fileinfo}""")
144 | 82 :
145 | 83 : for pattern in cov.split(","):
146 | 84 2 : if pattern.startsWith("!"):
147 | 85 2 : var pattern_noprefix = pattern
148 | 86 2 : removePrefix(pattern_noprefix, "!")
149 | 87 2 : for path in walkGlob(pattern_noprefix, "", options):
150 | 88 16 : exec(&"""lcov {lcov_args} --remove {fileinfo} "{path}*" -o {fileinfo}""")
151 | 89 : else:
152 | 90 : for path in walkGlob(pattern, "", options):
153 | 91 0 : exec(&"""lcov {lcov_args} --extract {fileinfo} "{path}*" -o {fileinfo}""")
154 | 92 :
155 | 93 : proc genhtml*(source="lcov.info", path="coverage", verbose=false, branch=false) =
156 | 94 2 : ## Generates the HTML code coverage report from a .info file generated by LCOV
157 | 95 2 : var lcov_args = "--quiet"
158 | 96 3 : if verbose:
159 | 97 1 : lcov_args = ""
160 | 98 3 : if branch:
161 | 99 1 : lcov_args &= " --branch-coverage"
162 | 100 : ## Generate LCOV Html report
163 | 101 6 : exec(&"genhtml {lcov_args} -o {path} {source}")
164 | 102 :
165 | 103 : proc coverage*(target="tests/**/*.nim", cov="!tests", verbose=false, branch=false, nimcache="nimcache", report_source="lcov.info", report_path="coverage", compiler=""): int =
166 | 104 2 : ## ____
167 | 105 : ##
168 | 106 : ## Code coverage for Nim:
169 | 107 : ## 1. Clean up past reports
170 | 108 : ## 2. Compile nim files in coverage mode
171 | 109 : ## 3. Run the the executables
172 | 110 : ## 4. Capture, produce and cleanup LCOV .info file
173 | 111 : ## 5. Generate the HTML report
174 | 112 : ##
175 | 113 2 : reset_coverage(report_source, report_path, nimcache)
176 | 114 :
177 | 115 2 : var targets = target.split(",")
178 | 116 : for target in targets:
179 | 117 2 : compile(target, nimcache, verbose, compiler)
180 | 118 :
181 | 119 2 : var lcov_args = build_lcov_args(verbose, branch)
182 | 120 :
183 | 121 6 : exec(&"lcov {lcov_args} --base-directory . --directory {nimcache} --zerocounters -q")
184 | 122 2 : for target in targets:
185 | 123 2 : trace(target)
186 | 124 :
187 | 125 6 : exec(&"lcov {lcov_args} --base-directory . --directory {nimcache} -c -o {report_source}")
188 | 126 :
189 | 127 2 : cleanup_report(report_source, cov, verbose, branch)
190 | 128 2 : genhtml(report_source, report_path, verbose, branch)
191 | 129 :
192 | 130 2 : result = 0
193 | 131 :
194 | 132 :
195 | 133 2 : when isMainModule:
196 | 134 : import cligen
197 | 135 1 : var helpOptions = {
198 | 136 : "target": "Nim files to compile and run in coverage mode. Support glob patterns.",
199 | 137 : "cov": "Path to folders and files you want in your code coverage report. Default takes your current directory and excludes your tests/ folder.",
200 | 138 2 : "verbose": "Displays every traces from LCOV and Nim"
201 | 139 : }
202 | 140 : dispatch(coverage,
203 | 141 : help = {
204 | 142 : "target": "Nim files to compile and run in coverage mode. Direct path or glob patterns.",
205 | 143 : "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. ",
206 | 144 : "verbose": "Displays all traces coming from LCOV and Nim",
207 | 145 : "branch": "Enables LCOV branch code coverage mode",
208 | 146 : "nimcache": "Nimcache path used by the Nim compiler",
209 | 147 : "report_source": "Path used by LCOV to generate the file .info",
210 | 148 : "report_path": "Folder path where the HTML code coverage report will be created",
211 | 149 : "compiler": "Forward your parameter(s) to the Nim compiler"
212 | 150 : },
213 | 151 : short = {
214 | 152 : "report_source": 's',
215 | 153 : "report_path": 'p',
216 | 154 : "compiler": 'o'
217 | 155 : }
218 | 156 : )
219 |
220 | |
221 |
222 |