gcovtools 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/project.rb ADDED
@@ -0,0 +1,146 @@
1
+ require_relative './file'
2
+ require_relative './line'
3
+ require_relative './logging'
4
+
5
+ module GCOVTOOLS
6
+
7
+ class Project
8
+
9
+ attr_reader :files, :stats
10
+ attr_accessor :name
11
+
12
+ def initialize name=""
13
+ @name = name
14
+ @files = {}
15
+ @adding = false
16
+ end
17
+
18
+ def <<(file)
19
+ if @files.has_key?file.name
20
+ @files[file.name].merge! file
21
+ else
22
+ @files[file.name] = file
23
+ end
24
+ _update_stats unless @adding
25
+ end
26
+
27
+ def files
28
+ @files.sort{|a,b|
29
+ if ::File.basename(a[0]) < ::File.basename(b[0])
30
+ -1
31
+ elsif ::File.basename(a[0]) == ::File.basename(b[0])
32
+ 0
33
+ else
34
+ 1
35
+ end }.map{|key,val|val}
36
+ end
37
+
38
+ def add_files &block
39
+ # suspend stat updates until done adding files
40
+ fail "add_files requires a block" unless block_given?
41
+
42
+ # guard against nested calls
43
+ was_adding = @adding
44
+ @adding = true
45
+ yield
46
+ @adding = was_adding
47
+
48
+ _update_stats unless @adding
49
+
50
+ end
51
+
52
+ def _update_stats
53
+ @stats = {
54
+ :missed_lines => 0,
55
+ :exec_lines => 0,
56
+ :empty_lines => 0,
57
+ :total_exec => 0,
58
+ :total_lines => 0,
59
+ :lines => 0,
60
+ :coverage => 0.0,
61
+ :hits_per_line => 0
62
+ }
63
+
64
+ @files.each do |name,file|
65
+ @stats[:missed_lines] += file.stats[:missed_lines]
66
+ @stats[:exec_lines] += file.stats[:exec_lines]
67
+ @stats[:total_exec] += file.stats[:total_exec]
68
+ @stats[:empty_lines] += file.stats[:empty_lines]
69
+ end
70
+
71
+ @stats[:lines] = @stats[:exec_lines] + @stats[:missed_lines]
72
+ @stats[:total_lines] = @stats[:lines] + @stats[:empty_lines]
73
+
74
+ if @stats[:lines] > 0
75
+ @stats[:coverage] = @stats[:exec_lines].to_f / @stats[:lines].to_f
76
+ @stats[:hits_per_line] = @stats[:total_exec].to_f / @stats[:lines]
77
+ else
78
+ @stats[:coverage] = 1
79
+ @stats[:hits_per_line] = 0
80
+ end
81
+
82
+ @stats[:coverage_s] = sprintf("%0.01f%",100.0*@stats[:coverage])
83
+ @stats[:hits_per_line_s] = sprintf("%0.02f",@stats[:hits_per_line])
84
+
85
+ end
86
+
87
+ def _add_file path, hash_={}
88
+ GCOVTOOLS::logger.info "parsing file: #{path}"
89
+
90
+ hash = hash_.dup
91
+
92
+ files = GCOVTOOLS::File.load(path)
93
+
94
+ # apply _inclusive_ filters first
95
+ files.select!{ |file|
96
+ hash[:include].nil? or hash[:include].empty? or !hash[:include].select{|f| f.match(::Pathname.new(file.meta['Source']).cleanpath.to_s) }.empty?
97
+ }
98
+
99
+ GCOVTOOLS::logger.info "included #{files.size} files"
100
+
101
+ old_size = files.size
102
+
103
+ # apply _exclusive_ filters next
104
+ files.select!{ |file|
105
+ hash[:exclude].nil? or hash[:exclude].empty? or hash[:exclude].select{|f| f.match(::Pathname.new(file.meta['Source']).cleanpath.to_s) }.empty?
106
+ }
107
+
108
+ GCOVTOOLS::logger.info "excluded #{old_size-files.size} files"
109
+
110
+ # add all the files that survived the gauntlet
111
+ files.map{ |file| self << file }
112
+ end
113
+
114
+ def add_dir path, hash_={}
115
+ GCOVTOOLS::logger.info "searching: #{path}"
116
+ hash = hash_.dup
117
+ if hash[:recursive] == true
118
+ filenames = Dir["#{path}/**/*.gcov"]
119
+ else
120
+ filenames = Dir["#{path}/*.gcov"]
121
+ end
122
+
123
+ GCOVTOOLS::logger.info "found: #{filenames}"
124
+
125
+ add_files do
126
+ filenames.each do |filename|
127
+ _add_file filename, hash
128
+ end # each filename
129
+ end # add_files
130
+ end # #add_dir
131
+
132
+ def add_file path, hash_={}
133
+ hash = hash_.dup
134
+ add_files do
135
+ _add_file path, hash
136
+ end # add_files
137
+ end # #add_file
138
+
139
+ def self.load_dir path, hash={}
140
+ project = GCOVTOOLS::Project.new
141
+ project.add_dir path, hash
142
+ project
143
+ end
144
+
145
+ end
146
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module GCOVTOOLS
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,58 @@
1
+
2
+ require_relative './project'
3
+ require_relative './file'
4
+ require_relative './line'
5
+
6
+ # require 'gyoku'
7
+ require 'builder'
8
+
9
+ module GCOVTOOLS
10
+
11
+ class XMLFormatter
12
+
13
+ def initialize project, va={}
14
+ @project = project
15
+ @xsl = va[:xsl]
16
+
17
+ # @hash = { :files => { :file => [] } }
18
+ @xml = Builder::XmlMarkup.new( :indent => 2 )
19
+ @xml.instruct! :xml, :encoding => "UTF-8"
20
+
21
+ if @xsl
22
+ @xml.instruct! :"xml-stylesheet", :href => @xsl, :type => "text/xsl"
23
+ end
24
+
25
+ @xml.projects do |xprojects|
26
+ xprojects.project(:name=>"Test") do |xproject|
27
+ xproject.files do |xfiles|
28
+ @project.files.each do |file|
29
+ xfiles.file(:name => file.name) do |xfile|
30
+ xfile.lines do |xlines|
31
+ file.lines.select{|line| line.number > 0}.each do |line|
32
+ xlines.line(:number=>line.number,:count=>line.count) do |xline|
33
+ xline.count line.count
34
+ xline.text do |xtext|
35
+ xtext.cdata! line.text
36
+ end # xtext
37
+ end # xline
38
+ end # line
39
+ end # xlines
40
+ end # xfile
41
+ end # file
42
+ end # xfiles
43
+ end # xproject
44
+ end # xprojects
45
+ end # initialize
46
+
47
+ def print
48
+ # puts "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
49
+ # if @xslt
50
+ # puts "<?xml-stylesheet href=\"#{@xslt}\" type=\"text/xsl\"?>"
51
+ # end
52
+ # puts Gyoku.xml( @hash )
53
+ puts @xml.target!
54
+ end # XMLFormatter#print
55
+
56
+ end # XMLFormatter
57
+
58
+ end # GCOVTOOLS
@@ -0,0 +1,35 @@
1
+ -: 0:Source:test.cpp
2
+ -: 0:Graph:test.gcno
3
+ -: 0:Data:test.gcda
4
+ -: 0:Runs:1
5
+ -: 0:Programs:1
6
+ -: 1:#include "test.h"
7
+ -: 2:
8
+ -: 3:int main( int argc, const char* argv[] )
9
+ -: 4:{
10
+ 1: 5: reached(2);
11
+ 1: 6: reached2(2);
12
+ 1: 7: reached3(2);
13
+ 1: 8: return 0;
14
+ -: 9:}
15
+ -: 0:Source:test1.cpp
16
+ -: 0:Graph:test1.gcno
17
+ -: 0:Data:test1.gcda
18
+ -: 0:Runs:2
19
+ -: 0:Programs:1
20
+ -: 1:
21
+ -: 2:int reached( int i )
22
+ -: 3:{
23
+ 2: 4: int j = i;
24
+ -: 5:
25
+ 2: 6: if( j == 2 )
26
+ 2: 7: return j;
27
+ -: 8:
28
+ #####: 9: return 3;
29
+ 2: 10:}
30
+ -: 11:
31
+ -: 12:int unreached( int x )
32
+ -: 13:{
33
+ #####: 14: int y = x;
34
+ #####: 15: return y+3;
35
+ -: 16:}
@@ -0,0 +1,22 @@
1
+ -: 0:Source:test3.cpp
2
+ -: 0:Graph:test3.gcno
3
+ -: 0:Data:test3.gcda
4
+ -: 0:Runs:4
5
+ -: 0:Programs:1
6
+ -: 1:
7
+ -: 2:int unreached3( int x )
8
+ -: 3:{
9
+ #####: 4: int y = x;
10
+ #####: 5: return y+3;
11
+ -: 6:}
12
+ -: 7:
13
+ -: 8:int reached3( int i )
14
+ -: 9:{
15
+ 4: 10: int j = i;
16
+ -: 11:
17
+ 4: 12: if( j == 2 )
18
+ 4: 13: return j;
19
+ -: 14:
20
+ #####: 15: return 3;
21
+ 4: 16:}
22
+ -: 17:
@@ -0,0 +1,14 @@
1
+ -: 0:Source:test.cpp
2
+ -: 0:Graph:test.gcno
3
+ -: 0:Data:test.gcda
4
+ -: 0:Runs:1
5
+ -: 0:Programs:1
6
+ -: 1:#include "test.h"
7
+ -: 2:
8
+ -: 3:int main( int argc, const char* argv[] )
9
+ -: 4:{
10
+ 1: 5: reached(2);
11
+ 1: 6: reached2(2);
12
+ 1: 7: reached3(2);
13
+ 1: 8: return 0;
14
+ -: 9:}
@@ -0,0 +1,21 @@
1
+ -: 0:Source:test1.cpp
2
+ -: 0:Graph:test1.gcno
3
+ -: 0:Data:test1.gcda
4
+ -: 0:Runs:2
5
+ -: 0:Programs:1
6
+ -: 1:
7
+ -: 2:int reached( int i )
8
+ -: 3:{
9
+ 2: 4: int j = i;
10
+ -: 5:
11
+ 2: 6: if( j == 2 )
12
+ 2: 7: return j;
13
+ -: 8:
14
+ #####: 9: return 3;
15
+ 2: 10:}
16
+ -: 11:
17
+ -: 12:int unreached( int x )
18
+ -: 13:{
19
+ #####: 14: int y = x;
20
+ #####: 15: return y+3;
21
+ -: 16:}
@@ -0,0 +1,22 @@
1
+ -: 0:Source:test2.cpp
2
+ -: 0:Graph:test2.gcno
3
+ -: 0:Data:test2.gcda
4
+ -: 0:Runs:3
5
+ -: 0:Programs:1
6
+ -: 1:
7
+ -: 2:int unreached2( int x )
8
+ -: 3:{
9
+ #####: 4: int y = x;
10
+ #####: 5: return y+3;
11
+ -: 6:}
12
+ -: 7:
13
+ -: 8:int reached2( int i )
14
+ -: 9:{
15
+ 3: 10: int j = i;
16
+ -: 11:
17
+ 3: 12: if( j == 2 )
18
+ 3: 13: return j;
19
+ -: 14:
20
+ #####: 15: return 3;
21
+ 3: 16:}
22
+ -: 17:
@@ -0,0 +1,140 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe GCOVTOOLS::File do
4
+
5
+ it "requires a filename" do
6
+ expect{ file = GCOVTOOLS::File.new }.to raise_error
7
+ end
8
+
9
+ describe "#name" do
10
+ it "returns the filename it was given" do
11
+ file = GCOVTOOLS::File.new "myfile.cpp"
12
+ expect(file.name).to eq("myfile.cpp")
13
+ end
14
+ end
15
+
16
+ describe "#<<" do
17
+ it "doesn't allow adding lines outside of the add_lines block" do
18
+ file = GCOVTOOLS::File.new "myfile.cpp"
19
+ expect{
20
+ file << GCOVTOOLS::Line.new(1,4,"line 1")
21
+ }.to raise_error
22
+ end
23
+ end
24
+
25
+ describe "#add_lines" do
26
+ it "computes stats after adding lines" do
27
+ file = GCOVTOOLS::File.new "myfile.cpp"
28
+ file.add_lines do
29
+ file << GCOVTOOLS::Line.new(1,4,"line 1")
30
+ file << GCOVTOOLS::Line.new(2,23,"line 2")
31
+ file << GCOVTOOLS::Line.new(3,:none,"line 3")
32
+ file << GCOVTOOLS::Line.new(4,:missed,"line 4")
33
+ file << GCOVTOOLS::Line.new(5,:missed,"line 5")
34
+ end
35
+ expect(file.stats[:lines]).to eq(4)
36
+ expect(file.stats[:total_lines]).to eq(5)
37
+ expect(file.stats[:total_exec]).to eq(27)
38
+ expect(file.stats[:empty_lines]).to eq(1)
39
+ expect(file.stats[:exec_lines]).to eq(2)
40
+ expect(file.stats[:missed_lines]).to eq(2)
41
+ expect(file.stats[:coverage]).to eq(0.5)
42
+ expect(file.stats[:hits_per_line]).to eq(27.0/4)
43
+ end
44
+
45
+ it "handles files with 0 executable lines" do
46
+ file = GCOVTOOLS::File.new "myfile.cpp"
47
+ file.add_lines do
48
+ file << GCOVTOOLS::Line.new(1,:none,"line 1")
49
+ file << GCOVTOOLS::Line.new(2,:none,"line 2")
50
+ file << GCOVTOOLS::Line.new(3,:none,"line 3")
51
+ file << GCOVTOOLS::Line.new(4,:none,"line 4")
52
+ end
53
+ expect(file.stats[:lines]).to eq(0)
54
+ expect(file.stats[:coverage]).to eq(1)
55
+ expect(file.stats[:hits_per_line]).to eq(0)
56
+ end
57
+ end
58
+
59
+ describe "#lines" do
60
+ it "returns no lines by default" do
61
+ file = GCOVTOOLS::File.new "myfile.cpp"
62
+ expect(file.lines.count).to eq(0)
63
+ end
64
+
65
+ it "should return the lines it was given, sorted by number" do
66
+ file = GCOVTOOLS::File.new "myfile.cpp"
67
+ file.add_lines do
68
+ file << GCOVTOOLS::Line.new(2,23,"line 2")
69
+ file << GCOVTOOLS::Line.new(1,4,"line 1")
70
+ end
71
+ expect(file.lines.count).to eq(2)
72
+ expect(file.lines[0].number).to eq(1)
73
+ expect(file.lines[1].number).to eq(2)
74
+ end
75
+
76
+ end
77
+
78
+ describe ".load" do
79
+ it "can load a gcov file" do
80
+ file = GCOVTOOLS::File.load(File.join(File.dirname(__FILE__),"data/test.cpp.gcov"))[0]
81
+ expect(file.lines.count).to eq(9)
82
+ expect(file.meta["Graph"]).to eq("test.gcno")
83
+ expect(file.meta["Runs"].to_i).to eq(1)
84
+ end
85
+
86
+ it "should split concatenated gcov files into multiple objects" do
87
+ files = GCOVTOOLS::File.load(File.join(File.dirname(__FILE__),"concat/test_cat.cpp.gcov"))
88
+
89
+ expect(files.count).to eq(2)
90
+ expect(files[0].meta["Source"]).to eq("test.cpp")
91
+ expect(files[1].meta["Source"]).to eq("test1.cpp")
92
+
93
+ expect(files[0].lines.count).to eq(9)
94
+ expect(files[0].meta["Graph"]).to eq("test.gcno")
95
+ expect(files[0].meta["Runs"].to_i).to eq(1)
96
+ end
97
+ end
98
+
99
+ describe ".demangle" do
100
+ it "should demangle file names with nonexisting components" do
101
+ demangled = GCOVTOOLS::File.demangle( "testfile.cpp.###Applications#Xcode.app#Nonexisting#Path#Contents#Developer#Toolchains#XcodeDefault.xctoolchain#usr#bin#^#include#c++#v1#streambuf.gcov" )
102
+ expect( demangled ).to eq("/Applications/Xcode.app/Nonexisting/Path/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/streambuf")
103
+ end
104
+ end
105
+
106
+ describe "#merge" do
107
+
108
+ it "should merge the counts of each line and recompute stats" do
109
+ file = GCOVTOOLS::File.new "myfile.cpp"
110
+ file.add_lines do
111
+ file << GCOVTOOLS::Line.new(1,4,"line 1")
112
+ file << GCOVTOOLS::Line.new(2,23,"line 2")
113
+ file << GCOVTOOLS::Line.new(3,:none,"line 3")
114
+ file << GCOVTOOLS::Line.new(4,:missed,"line 4")
115
+ file << GCOVTOOLS::Line.new(5,:missed,"line 5")
116
+ end
117
+
118
+ file2 = GCOVTOOLS::File.new "myfile.cpp"
119
+ file2.add_lines do
120
+ file2 << GCOVTOOLS::Line.new(1,1,"line 1")
121
+ file2 << GCOVTOOLS::Line.new(2,:missed,"line 2")
122
+ file2 << GCOVTOOLS::Line.new(3,:none,"line 3")
123
+ file2 << GCOVTOOLS::Line.new(4,4,"line 4")
124
+ file2 << GCOVTOOLS::Line.new(5,:missed,"line 5")
125
+ end
126
+
127
+ file3 = file.merge file2
128
+
129
+ expect(file3.stats[:lines]).to eq(4)
130
+ expect(file3.stats[:total_lines]).to eq(5)
131
+ expect(file3.stats[:total_exec]).to eq(32)
132
+ expect(file3.stats[:empty_lines]).to eq(1)
133
+ expect(file3.stats[:exec_lines]).to eq(3)
134
+ expect(file3.stats[:missed_lines]).to eq(1)
135
+ expect(file3.stats[:coverage]).to eq(0.75)
136
+ expect(file3.stats[:hits_per_line]).to eq(32.0/4)
137
+
138
+ end # it
139
+ end # describe
140
+ end
@@ -0,0 +1,93 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe GCOVTOOLS::Line do
4
+ describe "#number" do
5
+ it "returns the number it was given" do
6
+ line = GCOVTOOLS::Line.new 2, 4, "abcdef"
7
+ expect(line.number).to eq(2)
8
+ end
9
+ end
10
+
11
+ describe "#count" do
12
+ it "returns the count it was given" do
13
+ line = GCOVTOOLS::Line.new 2, 4, "abcdef"
14
+ expect(line.count).to eq(4)
15
+ end
16
+ end
17
+
18
+ describe "#text" do
19
+ it "returns the text it was given" do
20
+ line = GCOVTOOLS::Line.new 2, 4, "abcdef"
21
+ expect(line.text).to eq("abcdef")
22
+ end
23
+ end
24
+
25
+ describe ".parse" do
26
+ it "parses gcov format line with count" do
27
+ line = GCOVTOOLS::Line.parse " 2: 55: std::string StringUtil::ltrim( const std::string &s ) "
28
+ expect(line.number).to eq(55)
29
+ expect(line.count).to eq(2)
30
+ expect(line.text).to eq(" std::string StringUtil::ltrim( const std::string &s ) ")
31
+ end
32
+
33
+ it "parses empty gcov format line" do
34
+ line = GCOVTOOLS::Line.parse " -: 35: * don't worry about excessive copying."
35
+ expect(line.number).to eq(35)
36
+ expect(line.count).to eq(:none)
37
+ expect(line.text).to eq(" * don't worry about excessive copying.")
38
+ end
39
+
40
+ it "parses missed gcov format line" do
41
+ line = GCOVTOOLS::Line.parse " #####: 89: } "
42
+ expect(line.number).to eq(89)
43
+ expect(line.count).to eq(:missed)
44
+ expect(line.text).to eq(" } ")
45
+ end
46
+
47
+ end
48
+
49
+ describe "#state" do
50
+ it "should return :exec if it has positive count" do
51
+ line = GCOVTOOLS::Line.new 3,5,"line x"
52
+ expect(line.state).to eq(:exec)
53
+ end
54
+
55
+ it "should return :missed if it was missed" do
56
+ line = GCOVTOOLS::Line.new 3,:missed,"line x"
57
+ expect(line.state).to eq(:missed)
58
+ end
59
+
60
+ it "should return :none if it is not relevant" do
61
+ line = GCOVTOOLS::Line.new 3,:none,"line x"
62
+ expect(line.state).to eq(:none)
63
+ end
64
+ end
65
+
66
+ describe "#merge" do
67
+ it "should add hit counts" do
68
+ line = GCOVTOOLS::Line.new 3,4, "abcdef"
69
+ line2 = GCOVTOOLS::Line.new 3,1, "abcdef"
70
+ line3 = line.merge line2
71
+ expect(line3.count).to eq(5)
72
+
73
+ line = GCOVTOOLS::Line.new 3,:missed, "abcdef"
74
+ line2 = GCOVTOOLS::Line.new 3,1, "abcdef"
75
+ line3 = line.merge line2
76
+ expect(line3.count).to eq(1)
77
+
78
+ line = GCOVTOOLS::Line.new 3,1, "abcdef"
79
+ line2 = GCOVTOOLS::Line.new 3,:missed, "abcdef"
80
+ line3 = line.merge line2
81
+ expect(line3.count).to eq(1)
82
+ end
83
+
84
+ it "should give missed (0) presedence over none" do
85
+ line = GCOVTOOLS::Line.new 3,:missed, "abcdef"
86
+ line2 = GCOVTOOLS::Line.new 3,:none, "abcdef"
87
+ line3 = line.merge line2
88
+ expect(line3.count).to eq(:missed)
89
+ end
90
+
91
+ end # describe #merge
92
+
93
+ end