gcovtools 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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