grodowski / undercover

undercover warns about methods, classes and blocks that were changed without tests, to help you easily find untested code and reduce the number of bugs. It does so by analysing data from git diffs, code structure and SimpleCov coverage reports

Home Page:https://undercover-ci.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

undercover reports <100% method coverage even though it appears by eye to be 100%

gc-tim opened this issue · comments

Undercover does not show any red in the output, other than the initial "loc" line, and there are no "hits: 0/" or "branches: 0/" lines. Every line that is not "hits: n/a" is green.

Output from undercover:

undercover: 👮‍♂️ some methods have no test coverage! Please add specs for methods listed below
🚨 1) node `fun` type: instance method,
      loc: foo/applications/bar/services/baz.rb:92:168, coverage: 94.12%
 92:     def fun( hits: n/a
 93:       arg_a:, hits: n/a
 94:       arg_b:, hits: n/a
 95:       arg_c:, hits: n/a
 96:       arg_d:, hits: n/a
 97:       arg_e:, hits: n/a
 98:       arg_f:, hits: n/a
 99:       arg_g:, hits: n/a
100:       arg_h:, hits: n/a
101:       arg_i:, hits: n/a
102:       arg_j:, hits: n/a
103:       arg_k:, hits: n/a
104:       arg_l: hits: n/a
105:     ) hits: n/a
106:       var_a = fun_a(arg_b, arg_h, arg_l) hits: 181
107:       var_b = arg_e || arg_c hits: 181
108:       var_c = arg_g || arg_d hits: 181
109:       var_d = { hits: n/a
110:         key_a: arg_b.key_a, hits: 181
111:         key_b: var_b, hits: n/a
112:         key_c: var_c, hits: n/a
113:       }.merge(arg_i) hits: n/a
114: 
115:       if arg_k == :sym_a && (var_b.nil? || var_c.nil?) hits: 181 branches: 2/2
116:         return Result.new(var_a.fun_b, true, var_d) hits: 2
117:       end hits: n/a
118: 
119:       var_e = ModuleA::ClassA.new( hits: 179
120:         arg_f, hits: n/a
121:         var_b, hits: n/a
122:         var_c, hits: n/a
123:         nil, hits: n/a
124:         arg_k, hits: n/a
125:       ) hits: n/a
126: 
127:       var_f = var_a.find_unit( hits: 179
128:         var_e, hits: n/a
129:         key_d: arg_a, hits: n/a
130:         key_e: false, hits: n/a
131:         key_f: arg_j, hits: n/a
132:       ) hits: n/a
133: 
134:       if var_f hits: 179 branches: 1/1
135:         Result.new(var_f, false, var_d) hits: 125
136:       else hits: n/a
137:         var_f = var_a.find_unit( hits: 54
138:           var_e, hits: n/a
139:           key_d: arg_a, hits: n/a
140:           key_e: true, hits: n/a
141:           key_f: arg_j, hits: n/a
142:         ) hits: n/a
143:         if var_f hits: 54 branches: 1/1
144:           Result.new(var_f, true, var_d) hits: 32
145:         else hits: n/a
146:           raise( hits: 22
147:             RuntimeError.new( hits: n/a
148:               { hits: n/a
149:                 reason: 'Something sensible', hits: n/a
150: 
151:                 arg_k: arg_k, hits: n/a
152:                 arg_i: arg_i, hits: n/a
153:                 arg_d: arg_d, hits: n/a
154:                 arg_g: arg_g, hits: n/a
155:                 arg_f: arg_f, hits: n/a
156:                 key_a: arg_b&.key_a, hits: n/a
157:                 arg_a: arg_a, hits: n/a
158:                 arg_e: arg_e, hits: n/a
159:                 arg_c: arg_c, hits: n/a
160:                 arg_l: arg_l, hits: n/a
161:                 arg_h: arg_h, hits: n/a
162:                 arg_j: arg_j, hits: n/a
163:               }, hits: n/a
164:             ), hits: n/a
165:           ) hits: n/a
166:         end hits: n/a
167:       end hits: n/a
168:     end hits: n/a
Undercover finished in 0.953s

Relevant lines from lcov file:

…
SF:./foo/applications/bar/services/baz.rb
…
DA:92,1
DA:106,181
DA:107,181
DA:108,181
DA:110,181
DA:115,181
DA:116,2
DA:119,179
DA:127,179
DA:134,179
DA:135,125
DA:137,54
DA:143,54
DA:144,32
DA:146,22
…

Thank you for reporting this and sharing both the method snippet and related lcov. I suspect there could be an issue with how line coverage is interpreted in one or more of the multi-line statements...

Above bug could potentially live in Undercover::Result#uncovered? and Undercover::Result#coverage_f

That sounds very likely. A few days ago I encountered a similar issue where undercover was saying that coverage was below 100%, but its output showed all branches visited.

I eventually put a multi-line function call onto one line, and then it started to show branches: 3/4 on that line. One of the arguments was foo&.fun (with a safe navigation operator). It looks like that was the issue here as well, as I can now see key_a: arg_b&.key_a on line 156.

As for the specifics of how Undercover deals (or does not) with this case, I am afraid that I have no idea. 😂

Let me know if you need any more information.