-
Notifications
You must be signed in to change notification settings - Fork 15
/
coveragepy_parser.cr
143 lines (119 loc) · 3.11 KB
/
coveragepy_parser.cr
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
require "./base_parser"
require "sqlite3"
module CoverageReporter
class CoveragepyParser < BaseParser
def self.name
"python"
end
QUERY = <<-SQL
SELECT file.path, line_bits.numbits
FROM line_bits
INNER JOIN file ON (line_bits.file_id = file.id)
SQL
def globs : Array(String)
[
".coverage",
"**/*/.coverage",
]
end
def matches?(filename : String) : Bool
File.open(filename) do |f|
f.read_at(0, 15) do |io|
io.gets.try(&.downcase) == "sqlite format 3"
end
end
rescue Exception
false
end
def parse(filename : String) : Array(FileReport)
lines = {} of String => Array(Hits)
DB.open "sqlite3://#{filename}" do |db|
db.query(QUERY) do |rs|
rs.each do
name = rs.read(String)
numbits = rs.read(Slice(UInt8))
nums = [] of Hits
numbits.each_with_index do |byte, byte_i|
8.times do |bit_i|
if byte & (1 << bit_i) != 0
nums << (byte_i * 8 + bit_i).to_u64
end
end
end
lines[name] = nums
end
end
end
lines.map do |name, hits|
coverage = get_coverage(name, hits)
file_report(
name: name,
coverage: coverage,
)
end
end
private def get_coverage(name : String, hits : Array(Hits)) : Array(Hits?)
coverage = {} of Line => Hits?
line_no = 1.to_u64
under_def = false
docstring = false
brackets = 0
File.each_line(name, chomp: true) do |line|
code = line.strip
if code.ends_with?(/\(|\{|\[/)
brackets += code.count("({[")
brackets -= code.count(")}]")
end
if !docstring && code.starts_with?("\"\"\"")
if under_def || hits.find { |i| i == line_no }
docstring = true
coverage[line_no] = nil
next
end
end
# docstring
if docstring
if code.ends_with?("\"\"\"")
docstring = false
end
coverage[line_no] = nil
next
end
# comment
if code.starts_with?("#")
coverage[line_no] = nil
next
end
# a hit
if hits.find { |i| i == line_no }
coverage[line_no] = 1
next
end
# inside brackets
if brackets > 0
coverage[line_no] = nil
next
end
# empty string
if code.empty?
coverage[line_no] = nil
next
end
coverage[line_no] = 0
ensure
line_no += 1
if code
if brackets > 0 && code.ends_with?(/\)|\}|\]/)
brackets += code.count("({[")
brackets -= code.count(")}]")
end
under_def = code.starts_with?("def ") || code.starts_with?("class ")
end
end
coverage.keys.sort!.map { |k| coverage[k] }
rescue File::NotFoundError
Log.error("Couldn't open file #{name}")
[] of Hits?
end
end
end