cxxproject_gcov 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Christian Köstlin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # CxxprojectStats
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'cxxproject_stats'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install cxxproject_stats
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1,28 @@
1
+ body {
2
+ color: #000000;
3
+ background-color: #fff;
4
+ }
5
+
6
+ .source {
7
+ font-family: monospaced;
8
+ background-color: #fff;
9
+ }
10
+
11
+ .dead_code {
12
+ background-color: #FF6230;
13
+ }
14
+
15
+ .ignored_code {
16
+ }
17
+
18
+ .used_code {
19
+ background-color: #CAD7FE;;
20
+ }
21
+
22
+
23
+ .not_covered {
24
+ background-color: #FF6230;
25
+ }
26
+
27
+ .covered {
28
+ }
@@ -0,0 +1,167 @@
1
+ require 'ostruct'
2
+
3
+ module Gcov
4
+ @@excludes = []
5
+ class << self
6
+ def excludes
7
+ @excludes = [] unless @excludes
8
+ return @excludes
9
+ end
10
+ def excludes=(e)
11
+ @excludes = e
12
+ end
13
+ end
14
+
15
+ class FileStatistics
16
+ attr_reader :total_lines_of_code
17
+ def initialize(lines)
18
+ @total_lines_of_code = lines.select{|i|i.hit_count.counts?}.size
19
+ @dead_lines_of_code = lines.select{|i|i.hit_count.count == :DEAD_CODE}.size
20
+ @ignored_lines_of_code = lines.select{|i|i.hit_count.count == :IGNORED}.size
21
+ end
22
+ def covered_lines
23
+ return @total_lines_of_code - @dead_lines_of_code
24
+ end
25
+ def coverage
26
+ covered = covered_lines()
27
+ result = (100 * (covered.to_f / @total_lines_of_code)).to_i
28
+ if covered < @total_lines_of_code
29
+ if result == 100
30
+ result = 99
31
+ end
32
+ end
33
+ return result
34
+ end
35
+ end
36
+
37
+ class Hitcount
38
+ attr_reader :count
39
+ def counts?
40
+ if @count == :DEAD_CODE
41
+ return true
42
+ elsif @count == :IGNORED
43
+ return false
44
+ else
45
+ return true
46
+ end
47
+ end
48
+
49
+ def initialize(s)
50
+ if s.match('#+')
51
+ @count = :DEAD_CODE
52
+ elsif s == '-'
53
+ @count = :IGNORED
54
+ else
55
+ @count = s.to_i
56
+ end
57
+ end
58
+ def join(other)
59
+ if @count == :DEAD_CODE
60
+ if other.count == :DEAD_CODE
61
+ elsif other.count == :IGNORED
62
+ else
63
+ @count = other.count
64
+ end
65
+ elsif @count == :IGNORED
66
+ if other.count == :DEAD_CODE
67
+ @count = :DEAD_CODE
68
+ elsif other.count == :IGNORED
69
+ else
70
+ @count = other.count
71
+ end
72
+ else
73
+ if other.count == :DEAD_CODE
74
+ elsif other.count == :IGNORED
75
+ else
76
+ @count += other.count
77
+ end
78
+ end
79
+ self
80
+ end
81
+ end
82
+
83
+ def self.match_to_line(match)
84
+ =begin
85
+ lines look like this:
86
+ 4: 30: }
87
+ -: 31:
88
+ #####: 32: void Timestamp::unused() {
89
+ =end
90
+ hit_count = Hitcount.new(match[1].strip)
91
+ line_number = match[2].strip.to_i
92
+ code = match[3]
93
+ OpenStruct.new({:hit_count => hit_count, :line_number => line_number, :code => code})
94
+ end
95
+
96
+ def self.parse_gcov_string(str)
97
+ line_regexp = Regexp.new('(.*?):(.*?):(.*)')
98
+ matches = str.each_line.map{|i|i.match(line_regexp)}
99
+ return matches.map{|match|match_to_line(match)}.delete_if{|line|line.line_number == 0}
100
+ end
101
+
102
+ def self.simplify_path(p)
103
+ path_elements = p.split('/')
104
+ path_elements = path_elements.delete_if{|i|i == '.'}
105
+ new_elements = []
106
+ path_elements.each do |i|
107
+ if i == '..'
108
+ if new_elements.size > 0
109
+ new_elements.pop
110
+ else
111
+ new_elements << i
112
+ end
113
+ else
114
+ new_elements << i
115
+ end
116
+ end
117
+ return new_elements.join('/')
118
+ end
119
+
120
+ def self.replace_hash_with_slash(f)
121
+ f.gsub('#', '/')
122
+ end
123
+
124
+ def self.get_source_file(file)
125
+ res = ''
126
+ if not file.index('#')
127
+ # no #
128
+ res = file
129
+ elsif file.index('##')
130
+ # two or more ##
131
+ res = replace_hash_with_slash(file[0...file.index('##')])
132
+ else
133
+ # one #
134
+ res = replace_hash_with_slash(file)
135
+ end
136
+ return simplify_path(res.gsub('.gcov', ''))
137
+ end
138
+
139
+ def self.get_other_file(file)
140
+ res = ''
141
+ if not file.index('#')
142
+ # no #
143
+ return nil
144
+ elsif file.index('##')
145
+ # 2 or 3 ###
146
+ res = simplify_path(replace_hash_with_slash(file[file.index('##')+2..-1]).gsub('.gcov', ''))
147
+ else
148
+ return nil
149
+ end
150
+ end
151
+
152
+ def self.calc_source_and_visited(s)
153
+ return [get_source_file(s), get_other_file(s)]
154
+ end
155
+
156
+ # gcovs is array of gov-files
157
+ # gcov-files are arrays of lines
158
+ # lines are [Hitcount, linenr, content]
159
+ def self.merge_gcovs(gcovs)
160
+ gcovs.inject do |one,other|
161
+ one.zip(other).map do |a,b|
162
+ OpenStruct.new({:hit_count => a.hit_count.join(b.hit_count), :line_number => a.line_number, :code => a.code})
163
+ end
164
+ end
165
+ end
166
+
167
+ end
@@ -0,0 +1,91 @@
1
+ require 'cgi'
2
+ require 'cxxproject_gcov/gcov'
3
+ class HtmlExport
4
+
5
+ def initialize(out_dir, gcov_for_files, base_dir)
6
+ @out_dir = out_dir
7
+ @gcov_for_files = gcov_for_files
8
+ @base_dir = base_dir
9
+ export_css()
10
+ export_toc()
11
+ export_sources()
12
+ end
13
+ def export_css()
14
+ open_for_write(File.join(@out_dir, 'gcov.css')) do |out|
15
+ data = File.read(File.join(File.dirname(__FILE__), 'gcov.css'))
16
+ out.puts(data)
17
+ end
18
+ end
19
+ def export_toc
20
+ output_file_name = File.join(@out_dir, 'index.html')
21
+ open_for_write(output_file_name) do |out|
22
+ out.puts("<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"gcov.css\"></head><body>")
23
+
24
+ out.puts("<table><tr><th>Sourcefile</th><th>Loc</th><th>Coverage in %</th></tr>")
25
+ total_lines = 0
26
+ total_covered_lines = 0
27
+ @gcov_for_files.each do |file, coverage|
28
+ puts "working on #{file}"
29
+ relative_file = file.gsub(@base_dir, '')
30
+ stats = Gcov::FileStatistics.new(coverage)
31
+ total_lines += stats.total_lines_of_code
32
+ total_covered_lines += stats.covered_lines
33
+ coverage = stats.coverage
34
+ covered_style = coverage == 100 ? 'covered' : 'not_covered'
35
+ out.puts("<tr class=\"#{covered_style}\"><td><a href=\"#{relative_file}.html\">#{relative_file}</a></td><td>#{stats.total_lines_of_code}</td><td>#{coverage}</td></tr>")
36
+ end
37
+ covered = (100 * (total_covered_lines.to_f / total_lines)).to_i
38
+ out.puts("<tr><td>TOTAL</td><td>#{total_lines}</td><td>#{covered}</td></tr>")
39
+ out.puts("</table>")
40
+ out.puts("</body></html>")
41
+ end
42
+ end
43
+
44
+ def export_sources
45
+ @gcov_for_files.each do |file, coverage|
46
+ relative_file = file.gsub(@base_dir, '')
47
+ output_file_name = File.join(@out_dir, relative_file + '.html')
48
+ path_to_stylesheet = calc_path_to_stylesheet(output_file_name, @out_dir)
49
+ if path_to_stylesheet.size > 0
50
+ path_to_stylesheet = File.join(path_to_stylesheet, 'gcov.css')
51
+ else
52
+ path_to_stylesheet = 'gcov.css'
53
+ end
54
+ open_for_write(output_file_name) do |out|
55
+ out.puts("<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"#{path_to_stylesheet}\"></head><body><pre>")
56
+ coverage.each do |line|
57
+ line_number = sprintf("%5d:", line.line_number)
58
+ count_state = count_state_to_css_class(line.hit_count)
59
+ out.puts("<span class=\"#{count_state} linenumber\">#{line_number}</span><span class=\"#{count_state}\">#{CGI.escape_html(line.code)}</span>")
60
+ end
61
+ out.puts("</pre></body></html>")
62
+ end
63
+ end
64
+ end
65
+
66
+ def ensure_dir_for_file(output_file_name)
67
+ FileUtils.mkdir_p(File.dirname(output_file_name))
68
+ end
69
+
70
+ def open_for_write(output_file_name)
71
+ ensure_dir_for_file(output_file_name)
72
+ File.open(output_file_name, 'w') do |out|
73
+ yield out
74
+ end
75
+ end
76
+
77
+ def count_state_to_css_class(hit_count)
78
+ mapping = {:DEAD_CODE => 'dead_code', :IGNORED => 'ignored_code'}
79
+ if mapping.has_key?(hit_count.count)
80
+ return mapping[hit_count.count]
81
+ else
82
+ return 'used_code'
83
+ end
84
+ end
85
+
86
+ def calc_path_to_stylesheet(output, base)
87
+ count = output.split('/').size - base.split('/').size - 1
88
+ return ('../' * count)[0...-1]
89
+ end
90
+
91
+ end
@@ -0,0 +1,93 @@
1
+ require 'cxxproject_gcov/gcov'
2
+ require 'cxxproject_gcov/html_export'
3
+
4
+ cxx_plugin do |ruby_dsl, building_blocks, log|
5
+
6
+ def handle_has_sources(building_block)
7
+ if building_block.kind_of?(Cxxproject::HasSources)
8
+ building_block.collect_sources_and_toolchains.each do | source, toolchain |
9
+ cd(building_block.project_dir, :verbose => false) do
10
+ sh "gcov -l -p -o \"#{File.join(building_block.project_dir, building_block.get_object_file(source))}\" \"#{source}\""
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ def find_absolute_files(gcov_files)
17
+ return gcov_files.select{|f|f.index('###')}.map{|f|'/' + f.match(/.*###(.*)/)[1].gsub('#', '/').gsub('.gcov', '')}
18
+ end
19
+
20
+ def is_absolute?(file)
21
+ return file[0] == '/'
22
+ end
23
+
24
+ def find_building_block_for_source(building_blocks, to_find)
25
+ building_blocks.each do |building_block|
26
+ building_block.collect_sources_and_toolchains.each do |source, toolchain|
27
+ h = File.join(building_block.project_dir, source)
28
+ if h === to_find
29
+ return building_block
30
+ end
31
+ end
32
+ end
33
+ return nil
34
+ end
35
+
36
+ def find_file(file, building_blocks)
37
+ with_sources = building_blocks.select {|bb|bb.kind_of?(Cxxproject::HasSources)}
38
+ source, visited = Gcov.calc_source_and_visited(file)
39
+ if not visited
40
+ bb = find_building_block_for_source(with_sources, source)
41
+ return [source, bb]
42
+ else
43
+ if is_absolute?(visited)
44
+ return [visited, nil]
45
+ else
46
+ bb = find_building_block_for_source(with_sources, source)
47
+ if bb
48
+ return [Gcov.simplify_path(File.join(bb.project_dir, visited)), bb]
49
+ else
50
+ raise "could not find building block for #{source}"
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ directory ruby_dsl.build_dir
57
+
58
+ desc 'run gcov'
59
+ task :gcov => ruby_dsl.build_dir do
60
+ excludes = Gcov.excludes
61
+ sh 'find . -name "*.gcov" -delete'
62
+ Cxxproject::sorted_building_blocks.each do |building_block|
63
+ handle_has_sources(building_block)
64
+ end
65
+
66
+ gcovs_for_files = {}
67
+ bb_for_files = {}
68
+ gcov_files = Dir.glob('**/*.gcov')
69
+ gcov_files.each do |f|
70
+ f = File.join(Dir.pwd, f)
71
+ file, bb = find_file(f, Cxxproject::sorted_building_blocks)
72
+ raise "could not find file for #{f}" unless file
73
+
74
+ bb_for_files[file] = bb
75
+ gcovs_for_files[file] = [] unless gcovs_for_files.has_key?(file)
76
+ gcovs_for_files[file] << f
77
+ end
78
+
79
+ gcovs_for_files = gcovs_for_files.delete_if do | key, value |
80
+ excludes.any? do | i |
81
+ key.match(i)
82
+ end
83
+ end
84
+
85
+ gcovs_for_files.each do | file, gcovs |
86
+ gcovs_for_files[file] = Gcov.merge_gcovs(gcovs.map{|i|Gcov.parse_gcov_string(File.read(i))})
87
+ end
88
+
89
+
90
+ HtmlExport.new('out/gcov/html', gcovs_for_files, Dir.pwd + '/')
91
+ end
92
+
93
+ end
@@ -0,0 +1,3 @@
1
+ module CxxprojectGcov
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,5 @@
1
+ require "cxxproject_gcov/version"
2
+
3
+ module CxxprojectGcov
4
+ # Your code goes here...
5
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cxxproject_gcov
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Christian Köstlin
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-11-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: cxx
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: create gcvo files after a run
31
+ email:
32
+ - christian.koestlin@esrlabs.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - lib/cxxproject_gcov.rb
38
+ - lib/cxxproject_gcov/gcov.css
39
+ - lib/cxxproject_gcov/gcov.rb
40
+ - lib/cxxproject_gcov/plugin.rb
41
+ - lib/cxxproject_gcov/version.rb
42
+ - lib/cxxproject_gcov/html_export.rb
43
+ - LICENSE
44
+ - README.md
45
+ homepage: http://github.com
46
+ licenses:
47
+ - MIT
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubyforge_project:
66
+ rubygems_version: 1.8.25
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: ! '...'
70
+ test_files: []