covered 0.9.0 → 0.10.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 832fa815563de02d41b8036c455e30e77d2af630f3a40b7bedff85c538f60227
4
- data.tar.gz: 71b0144166e1a7285eb9e9db91ebd9904ec567646eb80a9f7d31dcaa7600c562
3
+ metadata.gz: 9ee9ff727d34b2454f900ea9f0b41e3b37801f0351da7b67e1def1925dd2e4e8
4
+ data.tar.gz: 7c9b15d856eab2589bfae31bddb1adf153b013038ff4cc13c4e407533da6b5f2
5
5
  SHA512:
6
- metadata.gz: aa48464fc936d9b2e30079c0fcd3bb8cd338f958f8e49a45bdfe063de94f2d6819970f36d13958c48d3c93d854274d6723574db8c4eaf88509d3cb0b2cf37db8
7
- data.tar.gz: 18cb7e0b47dddde9a80bfddc54762082ca55cea95215a14331c7ca8ae45325162063336f2b6ee2bb4106f506f612bc432d3f9275a21d404dca6ac4c94e08f0f9
6
+ metadata.gz: 1e93e9bb129a5960c85e958e8a4dcc7e1639e83269c6618eeffadd057aae6227d7e939707c51540893a2760002f152a3311e16d5022bc63893ab3b6d991e58eb
7
+ data.tar.gz: 8db25730358dbe19ecd5591780dd9e1c3d2a07193f31f300346673eea59b0986243d5735bca22c89f98e28ce0220336e2e190ed6a23e8176676a383e8c3ce3e0
data/.travis.yml CHANGED
@@ -1,7 +1,11 @@
1
- sudo: false
2
1
  language: ruby
2
+ dist: xenial
3
+ cache: bundler
3
4
 
4
- rvm:
5
- - 2.6
6
-
7
- before_install: gem install bundler -v 1.16.2
5
+ matrix:
6
+ include:
7
+ - rvm: 2.3
8
+ - rvm: 2.4
9
+ - rvm: 2.5
10
+ - rvm: 2.6
11
+ env: COVERAGE=BriefSummary,Coveralls
data/README.md CHANGED
@@ -59,6 +59,8 @@ When running `rspec`, you can specify the kind of coverage analysis you would li
59
59
  COVERAGE=Summary rspec
60
60
  ```
61
61
 
62
+ If no `COVERAGE` is specified, coverage tracking will be disabled.
63
+
62
64
  ### Partial Summary
63
65
 
64
66
  ```
@@ -69,12 +71,23 @@ This report only shows snippets of source code with incomplete coverage.
69
71
 
70
72
  ### Brief Summary
71
73
 
72
-
73
74
  ```
74
75
  COVERAGE=BriefSummary rspec
75
76
  ```
76
77
 
77
- This report lists several files in order of least coverage.
78
+ This report lists several files in order of least coverage.l
79
+
80
+ ### Coveralls/Travis Integration
81
+
82
+ You can send coverage information to [Coveralls](https://coveralls.io).
83
+
84
+ ```
85
+ COVERAGE=BriefSummary,Coveralls rspec
86
+ ```
87
+
88
+ This will print out a brief report and then upload the coverage data. This integrates transparently with Travis.
89
+
90
+ ###
78
91
 
79
92
  ## Contributing
80
93
 
data/covered.gemspec CHANGED
@@ -19,9 +19,10 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_dependency "rainbow"
22
-
23
22
  spec.add_dependency "parser"
24
23
 
24
+ spec.add_dependency "async-rest"
25
+
25
26
  spec.add_development_dependency "trenni", "~> 3.6"
26
27
 
27
28
  spec.add_development_dependency "bundler"
data/lib/covered.rb CHANGED
@@ -19,5 +19,4 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require_relative "covered/version"
22
-
23
22
  require_relative "covered/policy"
@@ -53,8 +53,16 @@ module Covered
53
53
 
54
54
  attr :annotations
55
55
 
56
+ def read(&block)
57
+ if block_given?
58
+ File.open(@path, "r", &block)
59
+ else
60
+ File.read(@path)
61
+ end
62
+ end
63
+
56
64
  def freeze
57
- return if frozen?
65
+ return self if frozen?
58
66
 
59
67
  @counts.freeze
60
68
  @annotations.freeze
@@ -65,6 +73,10 @@ module Covered
65
73
  super
66
74
  end
67
75
 
76
+ def to_a
77
+ @counts
78
+ end
79
+
68
80
  def zero?
69
81
  @total.zero?
70
82
  end
@@ -110,8 +122,12 @@ module Covered
110
122
 
111
123
  include Ratio
112
124
 
113
- def print_summary(output)
125
+ def print(output)
114
126
  output.puts "** #{executed_count}/#{executable_count} lines executed; #{percentage.to_f.round(2)}% covered."
115
127
  end
128
+
129
+ def to_s
130
+ "\#<#{self.class} path=#{@path} #{percentage.to_f.round(2)}% covered>"
131
+ end
116
132
  end
117
133
  end
@@ -0,0 +1,104 @@
1
+ # Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require 'async'
22
+ require 'async/rest/representation'
23
+
24
+ require 'securerandom'
25
+
26
+ module Covered
27
+ class Coveralls
28
+ class Wrapper < Async::REST::Wrapper::JSON
29
+ def prepare_request(payload, headers)
30
+ headers['accept'] ||= @content_type
31
+ boundary = SecureRandom.hex(32)
32
+
33
+ # This is a pretty messed up API. Don't change anything below. It's fragile.
34
+ if payload
35
+ headers['content-type'] = "multipart/form-data, boundary=#{boundary}"
36
+
37
+ Async::HTTP::Body::Buffered.new([
38
+ "--#{boundary}\r\n",
39
+ "Content-Disposition: form-data; name=\"json_file\"; filename=\"body.json\"\r\n",
40
+ "Content-Type: text/plain\r\n\r\n",
41
+ ::JSON.dump(payload),
42
+ "\r\n--#{boundary}--\r\n",
43
+ ])
44
+ end
45
+ end
46
+ end
47
+
48
+ URL = "https://coveralls.io/api/v1/jobs"
49
+
50
+ def initialize(token: nil, service: nil, job_id: nil)
51
+ @token = token
52
+ end
53
+
54
+ def detect_service
55
+ if token = ENV.fetch('COVERALLS_REPO_TOKEN', @token)
56
+ return {"repo_token" => token}
57
+ elsif @service && @job_id
58
+ return {"service_name" => @service, "service_job_id" => @job_id}
59
+ elsif job_id = ENV['TRAVIS_JOB_ID']
60
+ return {"service_name" => "travis-ci", "service_job_id" => job_id}
61
+ else
62
+ warn "#{self.class} can't detect service! Please specify COVERALLS_REPO_TOKEN."
63
+ end
64
+
65
+ return nil
66
+ end
67
+
68
+ def call(wrapper, output = $stderr)
69
+ if body = detect_service
70
+ output.puts "Submitting data using #{body.inspect}..."
71
+
72
+ source_files = []
73
+
74
+ wrapper.each do |coverage|
75
+ path = wrapper.relative_path(coverage.path)
76
+
77
+ source_files << {
78
+ name: path,
79
+ source_digest: Digest::MD5.hexdigest(coverage.read),
80
+ coverage: coverage.to_a,
81
+ }
82
+ end
83
+
84
+ body[:source_files] = source_files
85
+
86
+ Async do
87
+ representation = Async::REST::Representation.new(
88
+ Async::REST::Resource.for(URL),
89
+ wrapper: Wrapper.new
90
+ )
91
+
92
+ begin
93
+ response = representation.post(body)
94
+
95
+ output.puts "Got response: #{response.read}"
96
+
97
+ ensure
98
+ representation.close
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
data/lib/covered/files.rb CHANGED
@@ -44,28 +44,27 @@ module Covered
44
44
  return coverage
45
45
  end
46
46
 
47
- def each
48
- return to_enum unless block_given?
49
-
50
- @paths.each do |path, coverage|
51
- yield coverage
52
- end
47
+ def each(&block)
48
+ @paths.each_value(&block)
53
49
  end
54
50
  end
55
51
 
56
52
  class Include < Wrapper
57
- def initialize(output, pattern)
53
+ def initialize(output, pattern, base = "")
58
54
  super(output)
59
55
 
60
56
  @pattern = pattern
57
+ @base = base
61
58
  end
62
59
 
63
60
  attr :pattern
64
61
 
65
62
  def glob
66
63
  paths = Set.new
64
+ root = self.expand_path(@base)
65
+ pattern = File.expand_path(@pattern, root)
67
66
 
68
- Dir.glob(@pattern) do |path|
67
+ Dir.glob(pattern) do |path|
69
68
  unless File.directory?(path)
70
69
  paths << File.realpath(path)
71
70
  end
@@ -100,21 +99,30 @@ module Covered
100
99
 
101
100
  def each(&block)
102
101
  super do |coverage|
103
- yield coverage if accept?(coverage.path)
102
+ if accept?(coverage.path)
103
+ yield coverage
104
+ else
105
+ puts "Skipping #{coverage.path} #{self.class}"
106
+ end
104
107
  end
105
108
  end
106
109
  end
107
110
 
108
111
  class Skip < Filter
109
- def initialize(output, pattern)
112
+ def initialize(output, pattern, base = "")
110
113
  super(output)
111
114
 
112
115
  @pattern = pattern
116
+ @base = self.expand_path(base)
113
117
  end
114
118
 
115
119
  attr :pattern
116
120
 
117
121
  def accept? path
122
+ if @base
123
+ path = relative_path(path)
124
+ end
125
+
118
126
  !(@pattern === path)
119
127
  end
120
128
  end
@@ -142,6 +150,18 @@ module Covered
142
150
 
143
151
  attr :path
144
152
 
153
+ def expand_path(path)
154
+ File.expand_path(super, @path)
155
+ end
156
+
157
+ def relative_path(path)
158
+ if path.start_with?(@path)
159
+ path[@path.size+1..-1]
160
+ else
161
+ super
162
+ end
163
+ end
164
+
145
165
  def accept?(path)
146
166
  path.start_with?(@path)
147
167
  end
@@ -40,6 +40,6 @@ if ENV['COVERAGE']
40
40
 
41
41
  Minitest.after_run do
42
42
  $covered.disable
43
- $covered.print_summary($stderr)
43
+ $covered.call($stderr)
44
44
  end
45
45
  end
@@ -23,6 +23,8 @@ require_relative "files"
23
23
  require_relative "source"
24
24
  require_relative "capture"
25
25
 
26
+ require_relative 'coveralls'
27
+
26
28
  module Covered
27
29
  def self.policy(&block)
28
30
  policy = Policy.new
@@ -38,18 +40,14 @@ module Covered
38
40
  def initialize
39
41
  super(Files.new)
40
42
 
41
- @threshold = 1.0
42
- @summary_class = PartialSummary
43
+ @reports = []
43
44
  end
44
45
 
45
- attr_accessor :summary_class
46
- attr_accessor :threshold
47
-
48
46
  def freeze
49
- return if frozen?
47
+ return self if frozen?
50
48
 
51
49
  capture
52
- summary(threshold: @threshold)
50
+ @reports.freeze
53
51
 
54
52
  super
55
53
  end
@@ -90,12 +88,28 @@ module Covered
90
88
  capture.disable
91
89
  end
92
90
 
93
- def summary(*args)
94
- @summary ||= @summary_class.new(@output, *args)
91
+ attr :reports
92
+
93
+ def reports!(coverage = ENV['COVERAGE'])
94
+ if coverage
95
+ names = coverage.split(',')
96
+
97
+ names.each do |name|
98
+ if klass = Covered.const_get(name)
99
+ @reports << klass.new
100
+ else
101
+ warn "Could not find #{name} call Ignoring."
102
+ end
103
+ end
104
+ else
105
+ @reports << Covered::BriefSummary.new
106
+ end
95
107
  end
96
108
 
97
- def print_summary(*args)
98
- summary.print_summary(*args)
109
+ def call(*args)
110
+ @reports.each do |report|
111
+ report.call(self, *args)
112
+ end
99
113
  end
100
114
  end
101
115
  end
@@ -25,14 +25,12 @@ $covered = Covered.policy do
25
25
  root Dir.pwd
26
26
 
27
27
  # We will ignore any files in the test or spec directory:
28
- skip /test|spec/
28
+ skip /^(test|spec|vendor)/
29
29
 
30
30
  # We will include all files under lib, even if they aren't loaded:
31
31
  include "lib/**/*.rb"
32
32
 
33
33
  source
34
34
 
35
- if coverage = ENV['COVERAGE']
36
- self.summary_class = Covered.const_get(coverage) || Covered::BriefSummary
37
- end
35
+ reports!
38
36
  end
data/lib/covered/rspec.rb CHANGED
@@ -26,6 +26,7 @@ require 'rspec/core/formatters'
26
26
  module Covered
27
27
  module RSpec
28
28
  class Formatter
29
+ # The name `dump_summary` of this method is significant:
29
30
  ::RSpec::Core::Formatters.register self, :dump_summary
30
31
 
31
32
  def initialize(output)
@@ -33,7 +34,7 @@ module Covered
33
34
  end
34
35
 
35
36
  def dump_summary notification
36
- $covered.print_summary(@output)
37
+ $covered.call(@output)
37
38
  end
38
39
  end
39
40
 
@@ -110,11 +110,12 @@ module Covered
110
110
  else
111
111
  warn "Couldn't parse #{path}, file doesn't exist?"
112
112
  end
113
+ rescue
114
+ warn "Couldn't parse #{path}: #{$!}"
113
115
  end
114
116
 
115
117
  def each(&block)
116
118
  @output.each do |coverage|
117
- # This is a little bit inefficient, perhaps add a cache layer?
118
119
  if top = parse(coverage.path)
119
120
  self.expand(top, coverage)
120
121
  end
@@ -46,7 +46,7 @@ module Covered
46
46
 
47
47
  include Ratio
48
48
 
49
- def print_summary(output)
49
+ def print(output)
50
50
  output.puts "* #{count} files checked; #{executed_count}/#{executable_count} lines executed; #{percentage.to_f.round(2)}% covered."
51
51
  end
52
52
  end
@@ -24,17 +24,15 @@ require_relative 'wrapper'
24
24
  require 'rainbow'
25
25
 
26
26
  module Covered
27
- class Summary < Wrapper
28
- def initialize(output, threshold: 1.0)
29
- super(output)
30
-
27
+ class Summary
28
+ def initialize(threshold: 1.0)
31
29
  @threshold = threshold
32
30
  end
33
31
 
34
- def each
32
+ def each(wrapper)
35
33
  statistics = Statistics.new
36
34
 
37
- super do |coverage|
35
+ wrapper.each do |coverage|
38
36
  statistics << coverage
39
37
 
40
38
  if @threshold.nil? or coverage.ratio < @threshold
@@ -58,14 +56,16 @@ module Covered
58
56
  end
59
57
 
60
58
  # A coverage array gives, for each line, the number of line execution by the interpreter. A nil value means coverage is disabled for this line (lines like else and end).
61
- def print_summary(output = $stdout)
62
- statistics = self.each do |coverage|
59
+ def call(wrapper, output = $stdout)
60
+ statistics = self.each(wrapper) do |coverage|
63
61
  line_offset = 1
64
- output.puts "", Rainbow(coverage.path).bold.underline
62
+
63
+ path = wrapper.relative_path(coverage.path)
64
+ output.puts "", Rainbow(path).bold.underline
65
65
 
66
66
  counts = coverage.counts
67
67
 
68
- File.open(coverage.path, "r") do |file|
68
+ coverage.read do |file|
69
69
  file.each_line do |line|
70
70
  count = counts[line_offset]
71
71
 
@@ -91,30 +91,32 @@ module Covered
91
91
  end
92
92
  end
93
93
 
94
- coverage.print_summary(output)
94
+ coverage.print(output)
95
95
  end
96
96
 
97
- statistics.print_summary(output)
97
+ statistics.print(output)
98
98
  end
99
99
  end
100
100
 
101
101
  class BriefSummary < Summary
102
- def print_summary(output = $stdout, before: 4, after: 4)
102
+ def call(wrapper, output = $stdout, before: 4, after: 4)
103
103
  ordered = []
104
104
 
105
- statistics = self.each do |coverage|
105
+ statistics = self.each(wrapper) do |coverage|
106
106
  ordered << coverage unless coverage.complete?
107
107
  end
108
108
 
109
109
  output.puts
110
- statistics.print_summary(output)
110
+ statistics.print(output)
111
111
 
112
112
  if ordered.any?
113
113
  output.puts "", "Least Coverage:"
114
114
  ordered.sort_by!(&:missing_count).reverse!
115
115
 
116
116
  ordered.first(5).each do |coverage|
117
- output.write Rainbow(coverage.path).orange
117
+ path = wrapper.relative_path(coverage.path)
118
+
119
+ output.write Rainbow(path).orange
118
120
  output.puts ": #{coverage.missing_count} lines not executed!"
119
121
  end
120
122
  end
@@ -122,16 +124,18 @@ module Covered
122
124
  end
123
125
 
124
126
  class PartialSummary < Summary
125
- def print_summary(output = $stdout, before: 4, after: 4)
126
- statistics = self.each do |coverage|
127
+ def call(wrapper, output = $stdout, before: 4, after: 4)
128
+ statistics = self.each(wrapper) do |coverage|
127
129
  line_offset = 1
128
- output.puts "", Rainbow(coverage.path).bold.underline
130
+
131
+ path = wrapper.relative_path(coverage.path)
132
+ output.puts "", Rainbow(path).bold.underline
129
133
 
130
134
  counts = coverage.counts
131
135
  last_line = nil
132
136
 
133
137
  unless coverage.zero?
134
- File.open(coverage.path, "r") do |file|
138
+ coverage.read do |file|
135
139
  file.each_line do |line|
136
140
  range = Range.new([line_offset - before, 0].max, line_offset+after)
137
141
 
@@ -170,11 +174,11 @@ module Covered
170
174
  end
171
175
  end
172
176
 
173
- coverage.print_summary(output)
177
+ coverage.print(output)
174
178
  end
175
179
 
176
180
  output.puts
177
- statistics.print_summary(output)
181
+ statistics.print(output)
178
182
  end
179
183
  end
180
184
  end
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Covered
22
- VERSION = "0.9.0"
22
+ VERSION = "0.10.0"
23
23
  end
@@ -42,7 +42,23 @@ module Covered
42
42
 
43
43
  # @yield [Coverage] the path to the file, and the execution counts.
44
44
  def each(&block)
45
- @output.each(&block)
45
+ @output.each(&block) if @output
46
+ end
47
+
48
+ def relative_path(path)
49
+ if @output
50
+ @output.relative_path(path)
51
+ else
52
+ path
53
+ end
54
+ end
55
+
56
+ def expand_path(path)
57
+ if @output
58
+ @output.expand_path(path)
59
+ else
60
+ path
61
+ end
46
62
  end
47
63
 
48
64
  def to_h
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: covered
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-31 00:00:00.000000000 Z
11
+ date: 2019-02-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rainbow
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: async-rest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: trenni
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -126,6 +140,7 @@ files:
126
140
  - lib/covered.rb
127
141
  - lib/covered/capture.rb
128
142
  - lib/covered/coverage.rb
143
+ - lib/covered/coveralls.rb
129
144
  - lib/covered/eval.rb
130
145
  - lib/covered/files.rb
131
146
  - lib/covered/minitest.rb
@@ -156,7 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
171
  - !ruby/object:Gem::Version
157
172
  version: '0'
158
173
  requirements: []
159
- rubygems_version: 3.0.2
174
+ rubygems_version: 3.0.1
160
175
  signing_key:
161
176
  specification_version: 4
162
177
  summary: A modern approach to code coverage.