lcoveralls 0.1.0.pre.1

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/bin/lcoveralls ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/ruby -w
2
+ #
3
+ # Copyright 2014 Paul Colby
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ # Include our lib path, if not already.
19
+ lib_path = File.join(File.dirname(File.dirname(File.realpath(__FILE__))), 'lib')
20
+ $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
21
+
22
+ require 'lcoveralls'
23
+ Lcoveralls::Runner.new().run
@@ -0,0 +1,52 @@
1
+ #
2
+ # Copyright 2014 Paul Colby
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'logger'
18
+
19
+ module Lcoveralls
20
+
21
+ class ColorFormatter < Logger::Formatter
22
+
23
+ COLOR_CODES = {
24
+ 'Warning' => '35',
25
+ 'Error' => '31',
26
+ 'Fatal' => '31;1',
27
+ 'Unknown' => '31;1'
28
+ }
29
+
30
+ def initialize(color)
31
+ @color = color
32
+ end
33
+
34
+ def call(severity, datetime, progname, msg)
35
+ severity.capitalize!
36
+ if severity == 'Warn' then severity = 'Warning' end
37
+
38
+ if %w[Warning Error Fatal Unknown].include?(severity) then
39
+ msg = "#{severity}: #{msg}"
40
+ end
41
+
42
+ color_code = COLOR_CODES[severity] if @color
43
+ if color_code then
44
+ "\x1b[#{color_code}m#{msg}\x1b[0m\n"
45
+ else
46
+ "#{msg}\n"
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,53 @@
1
+ #
2
+ # Copyright 2014 Paul Colby
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'json'
18
+ require 'net/http'
19
+
20
+ module Lcoveralls
21
+
22
+ class CoverallsRequest < Net::HTTP::Post
23
+
24
+ def initialize(job, path='/api/v1/jobs')
25
+ super path
26
+ @boundary = (1...70).map { self.class.boundary_chr(rand(62)) }.join
27
+ set_content_type "multipart/form-data, boundary=#{@boundary}"
28
+ @body =
29
+ "--#{@boundary}\r\n" +
30
+ "Content-Disposition: form-data; name=\"json_file\"; filename=\"json_file\"\r\n" +
31
+ "Content-Type: application/json\r\n\r\n" +
32
+ JSON::generate(job) + "\r\n--#{@boundary}--\r\n"
33
+ end
34
+
35
+ # Note, 0-73 is valid for MIME, but only 0-61 is valid for HTTP headers.
36
+ def self.boundary_chr(index)
37
+ case index
38
+ when 0..9
39
+ index.to_s
40
+ when 10..35
41
+ ('a'.ord + index - 10).chr
42
+ when 36..61
43
+ ('A'.ord + index - 36).chr
44
+ when 62..73
45
+ "'()+_,-./:=?"[index - 62]
46
+ else
47
+ abort "Invalid boundary index #{index}"
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,27 @@
1
+ #
2
+ # Copyright 2014 Paul Colby
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'logger'
18
+
19
+ class Logger
20
+
21
+ TRACE = DEBUG - 1
22
+
23
+ def trace(progname = nil, &block)
24
+ add(TRACE, nil, progname, &block)
25
+ end
26
+
27
+ end
@@ -0,0 +1,84 @@
1
+ #
2
+ # Copyright 2014 Paul Colby
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'logger'
18
+ require 'optparse'
19
+
20
+ module Lcoveralls
21
+
22
+ class OptionParser
23
+
24
+ def parse!(args)
25
+ options = {
26
+ :color => $stderr.isatty,
27
+ :git => 'git',
28
+ :service => File.basename($0),
29
+ :severity => Logger::INFO
30
+ }
31
+
32
+ if ENV.has_key? 'TRAVIS_JOB_NUMBER' then
33
+ options[:service] = 'travis-ci'
34
+ options[:job_id] = ENV['TRAVIS_JOB_NUMBER']
35
+ end
36
+
37
+ parser = ::OptionParser.new do |o|
38
+ o.banner = "Usage: #{o.program_name} [options] [tracefile(s)]"
39
+ o.summary_width = 30
40
+ o.separator ''
41
+
42
+ o.separator 'Code / coveralls.io options:'
43
+ o.on( '--dryrun', 'Do not submit to coveralls.io' ) { options[:dryrun] = true }
44
+ o.on( '--export [PATH=stdout]', 'Export Coveralls job data') do |path|
45
+ options[:export] = case path
46
+ when 'stderr'; $stderr
47
+ when 'stdout'; $stdout
48
+ when nil ; $stdout
49
+ else File.new(path, 'w')
50
+ end
51
+ end
52
+ o.on('-r', '--root PATH', 'Set the path to the repo root') { |path| options[:root] = File.realpath(path) }
53
+ o.on('-s', '--service NAME','Set coveralls service name') { |name| options[:service] = name }
54
+ o.on('-t', '--token TOKEN', 'Set coveralls repo token') { |token| options[:token] = token }
55
+ o.separator ''
56
+
57
+ o.separator 'Stderr output options:'
58
+ o.on( '--[no-]color', 'Colorize output') { |color| options[:color] = color }
59
+ o.on('-d', '--debug', 'Enable debugging') { options[:severity] = Logger::DEBUG }
60
+ o.on( '--trace', 'Maximum output') { options[:severity] = Logger::TRACE }
61
+ o.on('-q', '--quiet', 'Show less output') { options[:severity] = options[:severity] + 1 }
62
+ o.on('-v', '--verbose', 'Show more output') { options[:severity] = options[:severity] - 1 }
63
+ o.separator ''
64
+
65
+ o.separator 'Miscellaneous options:'
66
+ o.on( '--[no-]git PATH', 'Path to the git program') { |path| options[:git] = path }
67
+ o.on('-h', '--help', 'Print usage text, then exit') { puts o; exit }
68
+ o.on( '--version', 'Print version number, then exit') { puts VERSION.join('.'); exit }
69
+ o.separator ''
70
+ end
71
+
72
+ begin
73
+ parser.parse! args
74
+ options
75
+ rescue ::OptionParser::InvalidOption => e
76
+ $stderr.puts parser
77
+ $stderr.puts e
78
+ exit!
79
+ end
80
+ end
81
+
82
+ end
83
+
84
+ end
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/ruby -w
2
+ #
3
+ # Copyright 2014 Paul Colby
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require 'find'
19
+
20
+ module Lcoveralls
21
+ class Runner
22
+
23
+ def initialize
24
+ # Parse the command line options.
25
+ parser = Lcoveralls::OptionParser.new
26
+ @options = parser.parse! ARGV
27
+
28
+ # Setup a logger instance.
29
+ @log = Logger.new(STDERR)
30
+ @log.formatter = Lcoveralls::ColorFormatter.new @options[:color]
31
+ @log.sev_threshold = @options[:severity]
32
+ @log.debug { "Options: #{@options}" }
33
+ end
34
+
35
+ def find_root(info_files)
36
+ # Try source file(s) covered by the lcov tracefile(s).
37
+ root_dirs = Hash.new(0)
38
+ info_files.each do |file|
39
+ File.open(file).each do |line|
40
+ line.match(/^SF:(.*)$/) do |match|
41
+ Dir.chdir(File.dirname(match[1])) do
42
+ root_dir = `"#{@options[:git]}" rev-parse --show-toplevel`.rstrip
43
+ root_dirs[root_dir] = root_dirs[root_dir] + 1 unless root_dir.empty?
44
+ end if Dir.exist?(File.dirname(match[1]))
45
+ end
46
+ end
47
+ end
48
+
49
+ if root_dirs.empty?
50
+ nil
51
+ elsif root_dirs.size == 1
52
+ root_dirs.shift[0]
53
+ else
54
+ root_dir = root_dirs.max_by { |key, value| value }[0]
55
+ @log.warn "Found multiple possible repo roots. Settled on #{root_dir}"
56
+ root_dir
57
+ end
58
+ end
59
+
60
+ def get_percentage(lines_hit, lines_found, bold=false)
61
+ perc = lines_hit.to_f / lines_found.to_f * 100.0
62
+ color = case when perc >= 90; 32 when perc >= 75; 33 else 31 end
63
+ if bold then color = "1;#{color}" end
64
+ perc = perc.finite? ? format('%5.1f%', perc) : ' ' * 6
65
+ perc = "\x1b[#{color}m#{perc}\x1b[0m" if @options[:color] and color
66
+ perc
67
+ end
68
+
69
+ def get_source_files(info_files, root_dir)
70
+ sources = {}
71
+ total_lines_found = 0
72
+ total_lines_hit = 0
73
+ info_files.each do |file|
74
+ @log.debug "Processing tracefile: #{file}"
75
+ source_pathname = nil
76
+ in_record = false
77
+ lines_found = nil
78
+ lines_hit = nil
79
+ File.open(file).each do |line|
80
+ @log.debug "#{file}: #{line.rstrip}"
81
+
82
+ # SF:<absolute path to the source file>
83
+ line.match('^SF:' + Regexp.quote(root_dir) + '/(.*)$') do |match|
84
+ @log.warn 'Found source filename without preceding end_of_record' if in_record
85
+ @log.debug "Found source filename: #{match[1]}"
86
+ source_pathname = match[1]
87
+ if !sources.has_key?(source_pathname) then
88
+ source = File.read(match[1])
89
+ sources[source_pathname] = {
90
+ :name => source_pathname,
91
+ :source => source,
92
+ :coverage => Array.new(source.lines.count)
93
+ }
94
+ end
95
+ in_record = true
96
+ end
97
+
98
+ # DA:<line number>,<execution count>[,<checksum>]
99
+ line.match(/^DA:(?<line>\d+),(?<count>\d+)(,(?<checksum>.*))?$/) do |match|
100
+ line_index = match[:line].to_i - 1
101
+ if !sources[source_pathname][:coverage][line_index] then
102
+ sources[source_pathname][:coverage][line_index] = 0
103
+ end
104
+ sources[source_pathname][:coverage][line_index] =
105
+ sources[source_pathname][:coverage][line_index] + match[:count].to_i;
106
+ end if in_record
107
+
108
+ # LF:<lines found> or LH:<lines hit>
109
+ line.match(/^LF:(?<count>\d+)$/) { |match| lines_found = match[:count] }
110
+ line.match(/^LH:(?<count>\d+)$/) { |match| lines_hit = match[:count] }
111
+
112
+ # end_of_record
113
+ if line == "end_of_record\n" and in_record then
114
+ @log.info begin
115
+ perc = get_percentage(lines_hit, lines_found)
116
+ "[#{perc}] #{source_pathname} (#{lines_hit}/#{lines_found})"
117
+ end
118
+ total_lines_found = total_lines_found + lines_found.to_i
119
+ total_lines_hit = total_lines_hit + lines_hit.to_i
120
+ in_record = false
121
+ lines_found = nil
122
+ lines_hit = nil
123
+ end
124
+ end
125
+ end
126
+
127
+ @log.info begin
128
+ perc = get_percentage(total_lines_hit, total_lines_found, true)
129
+ "[#{perc}] Total (#{total_lines_hit}/#{total_lines_found})"
130
+ end
131
+
132
+ sources.values
133
+ end
134
+
135
+ def get_git_info(root_dir)
136
+ Dir.chdir(root_dir) do
137
+ info = {
138
+ :head => {
139
+ :id => `"#{@options[:git]}" show --format='%H' --no-patch`.rstrip,
140
+ :author_name => `"#{@options[:git]}" show --format='%an' --no-patch`.rstrip,
141
+ :author_email => `"#{@options[:git]}" show --format='%ae' --no-patch`.rstrip,
142
+ :commiter_name => `"#{@options[:git]}" show --format='%cn' --no-patch`.rstrip,
143
+ :commiter_email => `"#{@options[:git]}" show --format='%ce' --no-patch`.rstrip,
144
+ :message => `"#{@options[:git]}" show --format='%B' --no-patch`.rstrip,
145
+ },
146
+ :branch => `"#{@options[:git]}" rev-parse --abbrev-ref HEAD`.rstrip,
147
+ :remotes => []
148
+ }
149
+
150
+ `"#{@options[:git]}" remote --verbose`.each_line do |line|
151
+ line.match(/^(?<name>\S+)\s+(?<url>\S+)(\s+\((fetch|push)\))?/) do |match|
152
+ info[:remotes] << Hash[match.names.zip(match.captures)]
153
+ end
154
+ end
155
+ info[:remotes].uniq!
156
+ info.delete(:remotes) if info[:remotes].empty?
157
+
158
+ info
159
+ end if Dir.exist?(root_dir)
160
+ end
161
+
162
+ def run
163
+ # Find *.info tracefiles if none specified on the command line.
164
+ Find.find('.') do |path|
165
+ @log.trace { "Looking for tracefiles: #{path}" }
166
+ if path =~ /.*\.info$/ then
167
+ @log.info { "Found tracefile: #{path}" }
168
+ ARGV << path
169
+ end
170
+ end unless ARGV.any?
171
+
172
+ @options[:root] = find_root(ARGV) unless @options.include?(:root)
173
+ if !@options[:root] then
174
+ @log.error 'Root not specified, nor detected; consider using --root'
175
+ exit!
176
+ end
177
+
178
+ # Build the coveralls.io job request.
179
+ job = {}
180
+ job[:repo_token] = @options[:token] if @options.has_key? :token
181
+ job[:service_name] = @options[:service] if @options.has_key? :service
182
+ job[:service_job_id] = @options[:job_id] if @options.has_key? :job_id
183
+ if !job.has_key?(:token) and !job.has_key?(:service_job_id) then
184
+ @log.warn 'No service job id detected; consider using --token'
185
+ end
186
+ job[:source_files] = get_source_files(ARGV, @options[:root])
187
+ job[:git] = get_git_info(@options[:root]) unless !@options[:git]
188
+ job[:run_at] = Time.new
189
+ request = Lcoveralls::CoverallsRequest.new(job)
190
+ @log.trace { request.body }
191
+
192
+ # If asked to, export the Coveralls API job request JSON document.
193
+ if @options.has_key? :export then
194
+ @options[:export].write(JSON::pretty_generate job);
195
+ end
196
+
197
+ # Send (if not in dryrun mode) the Coveralls API request.
198
+ uri = URI('https://coveralls.io/api/v1/jobs')
199
+ http = Net::HTTP.new(uri.host, uri.port)
200
+ http.use_ssl = true
201
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
202
+
203
+ if !@options[:dryrun] then
204
+ @log.debug { "Sending #{request.body.size} bytes to coveralls.io" }
205
+ res = http.request(request)
206
+ puts res.body if res
207
+ end
208
+ end
209
+
210
+ end
211
+ end
@@ -0,0 +1,19 @@
1
+ #
2
+ # Copyright 2014 Paul Colby
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ module Lcoveralls
18
+ VERSION = [ 0, 1, 0, 'pre', 1 ]
19
+ end
data/lib/lcoveralls.rb ADDED
@@ -0,0 +1,22 @@
1
+ #
2
+ # Copyright 2014 Paul Colby
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'lcoveralls/color_formatter'
18
+ require 'lcoveralls/coveralls_request'
19
+ require 'lcoveralls/logger'
20
+ require 'lcoveralls/option_parser'
21
+ require 'lcoveralls/runner'
22
+ require 'lcoveralls/version'
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lcoveralls
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.pre.1
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Paul Colby
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2010-04-28 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Lcoveralls is a simple script for reporting code coverage results from
15
+ LCOV to Coveralls.
16
+ email: ruby@colby.id.au
17
+ executables:
18
+ - lcoveralls
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - lib/lcoveralls.rb
23
+ - lib/lcoveralls/color_formatter.rb
24
+ - lib/lcoveralls/option_parser.rb
25
+ - lib/lcoveralls/version.rb
26
+ - lib/lcoveralls/logger.rb
27
+ - lib/lcoveralls/runner.rb
28
+ - lib/lcoveralls/coveralls_request.rb
29
+ - bin/lcoveralls
30
+ homepage: https://github.com/pcolby/lcoveralls
31
+ licenses:
32
+ - Apache-2.0
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ! '>='
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ! '>'
47
+ - !ruby/object:Gem::Version
48
+ version: 1.3.1
49
+ requirements: []
50
+ rubyforge_project:
51
+ rubygems_version: 1.8.23
52
+ signing_key:
53
+ specification_version: 3
54
+ summary: Report Gcov / LCOV (ie C, C++, Go, etc) code coverage to coveralls.io
55
+ test_files: []