simplecov 1.0.2 → 1.0.3

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: 1f159cd5427690b20f11c1e4a3f1a90c23c16f5f65e2043205bf132a5abc0218
4
- data.tar.gz: d5a28a2c529ff2b59652bd0c1ba1f9d649c7c24fd02521905c6d2cbe6b1eeae3
3
+ metadata.gz: ddfad3987b6f04c755c08c2b251d111ca75eff0e14aef5156e9d30f049087e96
4
+ data.tar.gz: 97efb84c53398cc92a3e2106d2428351f3f1edc37cc4a7c34abeb2859503a0fd
5
5
  SHA512:
6
- metadata.gz: 792c138d327b5d305ebe273dfd5a97d70eb8104102557d5ae6452a26d5cc64e1db7720c3de5e2c556a78b22561c53778579909ddef7f4615e1c447f041fa921c
7
- data.tar.gz: 9f6479ceabbd5f6fc24585a2134a0f4e1270dbd988922d4d387e5695055b811655999b3d95c2a9b31c960cf0c22aa884d5bf94eae93ca6fb97868bbec0de3aab
6
+ metadata.gz: ac211a4d93fe0db4e87f4ddddff178566e84ef2d04234ffba49382da236668f718ef43d2b68b01977e1aa17dec86823dbc70cb8adf7eb116a9f24f06a99d78c2
7
+ data.tar.gz: 5259cbb289b03d111995433363370d3e91729da55c9aef5834a0626cfd11fc93fa6614fab3f764e8faaa667439a6716cb1e842f343c58875fd83c023977f6c99
data/README.md CHANGED
@@ -1042,6 +1042,20 @@ SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter
1042
1042
  `SimpleCov.result.format!` then invokes `SimpleCov::Formatter::YourFormatter.new.format(result)`, where `result` is a
1043
1043
  `SimpleCov::Result`. Do whatever you wish with it.
1044
1044
 
1045
+ ### Passing options to formatters
1046
+
1047
+ Anywhere a formatter class is accepted, a ready-built instance works too — that's how you reach constructor options.
1048
+ The built-in HTML and JSON formatters take `silent: true` to suppress the "Coverage report generated" status line on
1049
+ stderr, and `output_dir:` to write the report somewhere other than `SimpleCov.coverage_path`:
1050
+
1051
+ ```ruby
1052
+ SimpleCov.start do
1053
+ formatter SimpleCov::Formatter::HTMLFormatter.new(silent: true)
1054
+ end
1055
+ ```
1056
+
1057
+ Instances mix freely with classes in `formatters` lists as well.
1058
+
1045
1059
  ### Using multiple formatters
1046
1060
 
1047
1061
  As of SimpleCov 0.9 you can specify multiple result formats. The HTML and JSON formatters are built in; other
@@ -16,14 +16,15 @@ module SimpleCov
16
16
  #
17
17
  # Method coverage maps `[class, name, start_line, start_col, end_line,
18
18
  # end_col]` keys to hit counts. Keys are matched on their SOURCE
19
- # identity — (name, location), ignoring the class element because
20
- # Ruby records one entry per receiver: the same `define_method` block
21
- # defined onto different classes in different processes arrives with
22
- # different (normalized) receivers for the same source method, and
23
- # matching on the full key would keep both, letting a never-called
24
- # receiver's 0 shadow a covered method after merge (issue #1234).
25
- # Combining sums the hit counts for matching methods and preserves
26
- # methods that only appear in one result.
19
+ # identity — the location, ignoring the class and name elements
20
+ # because Ruby records one entry per defined method: the same
21
+ # `define_method` block defined onto different classes, or under
22
+ # different names, in different processes arrives with different
23
+ # receivers or names for the same source method, and matching on the
24
+ # full key would keep both, letting a never-called copy's 0 shadow a
25
+ # covered method after merge (issue #1234). Combining sums the hit
26
+ # counts for matching methods and preserves methods that only appear
27
+ # in one result.
27
28
  #
28
29
  # @return [Hash]
29
30
  #
@@ -41,8 +42,8 @@ module SimpleCov
41
42
  end
42
43
 
43
44
  def source_identity(key)
44
- _class_name, *identity = SourceFile::RubyDataParser.call(key)
45
- identity
45
+ _class_name, _method_name, *location = SourceFile::RubyDataParser.call(key)
46
+ location
46
47
  end
47
48
  end
48
49
  end
@@ -9,11 +9,16 @@ module SimpleCov
9
9
  attr_writer :formatter, :print_error_status
10
10
 
11
11
  #
12
- # Gets or sets the configured formatter. Pass `false` (or `nil`) to
13
- # opt out of formatting entirely worker processes in big parallel
14
- # CI setups (see #964) only need their `.resultset.json` on disk so
15
- # a final `SimpleCov.collate` job can produce the report; running
16
- # them without a formatter saves the per-job HTML/multi-formatter
12
+ # Gets or sets the configured formatter. Accepts a formatter class
13
+ # (instantiated fresh for every report) or a ready-built instance,
14
+ # which is how constructor options are passed — e.g.
15
+ # `formatter SimpleCov::Formatter::HTMLFormatter.new(silent: true)`
16
+ # to suppress the "Coverage report generated" status line (see
17
+ # #1240). Pass `false` (or `nil`) to opt out of formatting
18
+ # entirely — worker processes in big parallel CI setups (see #964)
19
+ # only need their `.resultset.json` on disk so a final
20
+ # `SimpleCov.collate` job can produce the report; running them
21
+ # without a formatter saves the per-job HTML/multi-formatter
17
22
  # overhead.
18
23
  #
19
24
  def formatter(formatter = :__no_arg__)
@@ -40,7 +45,8 @@ module SimpleCov
40
45
 
41
46
  # Sets the configured formatters. Equivalent to `formatters [...]`.
42
47
  # Accepts a single formatter as well as an Array, matching the pre-1.0 behavior
43
- # where `MultiFormatter.new` normalized its input.
48
+ # where `MultiFormatter.new` normalized its input. Elements may be
49
+ # formatter classes or ready-built instances; see `formatter`.
44
50
  def formatters=(formatters)
45
51
  formatters = Array(formatters)
46
52
  @formatter = formatters.empty? ? nil : SimpleCov::Formatter::MultiFormatter.new(formatters)
@@ -10,7 +10,7 @@ module SimpleCov
10
10
  module InstanceMethods
11
11
  def format(result)
12
12
  formatters.map do |formatter|
13
- formatter.new.format(result)
13
+ Formatter.instance_for(formatter).format(result)
14
14
  rescue StandardError => e
15
15
  warn("Formatter #{formatter} failed with #{e.class}: #{e.message} (#{(_ = e.backtrace).first})")
16
16
  nil
@@ -6,6 +6,13 @@ module SimpleCov
6
6
  # and can be wired up via `SimpleCov.formatter=`.
7
7
  # TODO: Documentation on how to build your own formatters
8
8
  module Formatter
9
+ # Formatters can be configured either as classes (instantiated
10
+ # fresh for every report) or as ready-built instances — the only
11
+ # way to reach constructor options like
12
+ # `HTMLFormatter.new(silent: true)`. See #1240.
13
+ def self.instance_for(formatter)
14
+ formatter.is_a?(Class) ? formatter.new : formatter
15
+ end
9
16
  end
10
17
  end
11
18
 
@@ -102,7 +102,7 @@ module SimpleCov
102
102
  formatter = SimpleCov.formatter
103
103
  return nil if formatter.nil?
104
104
 
105
- formatter.new.format(self)
105
+ Formatter.instance_for(formatter).format(self)
106
106
  end
107
107
 
108
108
  # Defines when this result has been created. Defaults to Time.now
@@ -60,37 +60,69 @@ module SimpleCov
60
60
  SINGLETON_WRAPPER_PATTERN = /\A#<Class:([A-Z_][\w:]*)>\z/
61
61
  private_constant :SINGLETON_WRAPPER_PATTERN
62
62
 
63
- # Ruby's method coverage records one entry per RECEIVER, not per source
64
- # location: a block handed to `define_method` / `define_singleton_method`
65
- # from a shared code path (a module's `included` hook, a builder) yields
66
- # a separate `[receiver, name, location]` entry for every class it's
67
- # defined on, all pointing at the same source. A file-based report can
68
- # only express "was the method at this location ever executed", so
69
- # entries are aggregated by (name, location), summing hits otherwise
70
- # each receiver whose copy never ran shows as a phantom uncovered method
71
- # on a line whose line coverage is 100% (issue #1234). The first entry's
72
- # (normalized) receiver is kept for display.
63
+ # Ruby's method coverage records one entry per DEFINED METHOD, not per
64
+ # source location: a block handed to `define_method` /
65
+ # `define_singleton_method` from a shared code path yields a separate
66
+ # `[receiver, name, location]` entry for every class it's defined on
67
+ # (a module's `included` hook defining onto each descendant) AND for
68
+ # every name it's defined under (a builder looping `define_method key`
69
+ # over a container), all pointing at the same source. A file-based
70
+ # report can only express "was the method at this location ever
71
+ # executed", so entries are aggregated by location alone, summing
72
+ # hits — otherwise each receiver or name whose generated copy never
73
+ # ran shows as a phantom uncovered method on a line whose line
74
+ # coverage is 100%. Regular `def`s map one location to one name, so
75
+ # they are unaffected. The first entry's (normalized) key is kept for
76
+ # display. See issue #1234.
73
77
  def normalize_method_keys(cover_statistic)
74
78
  methods = cover_statistic[:methods]
75
79
  return unless methods
76
80
 
77
81
  aggregated = {} #: Hash[untyped, [untyped, Integer]]
78
82
  methods.each_with_object(aggregated) do |(key, count), memo|
79
- identity = key[1..] #: Array[untyped]
80
- retained_key, existing = memo[identity] || [normalize_method_key(key), 0]
81
- memo[identity] = [retained_key, existing + count]
83
+ location = key[2..] #: Array[untyped]
84
+ retained_key, existing = memo[location] || [normalize_method_key(key), 0]
85
+ memo[location] = [retained_key, existing + count]
82
86
  end
83
87
  cover_statistic[:methods] = aggregated.values.to_h
84
88
  end
85
89
 
86
90
  def normalize_method_key(key)
87
91
  normalized_key = key.dup
88
- normalized_key[0] = key[0].to_s
89
- .gsub(ADDRESS_PATTERN, ADDRESS_PLACEHOLDER)
90
- .sub(SINGLETON_WRAPPER_PATTERN, '\1')
92
+ normalized_key[0] = class_display_name(key[0])
93
+ .gsub(ADDRESS_PATTERN, ADDRESS_PLACEHOLDER)
94
+ .sub(SINGLETON_WRAPPER_PATTERN, '\1')
91
95
  normalized_key
92
96
  end
93
97
 
98
+ # Rendering a class name can execute user code: a singleton class's
99
+ # `to_s` renders its attached object via `#inspect`, which a module can
100
+ # shadow with an incompatible signature (Liquid::Utils defines
101
+ # `inspect(value, max_depth = 2)` as a module_function, so rendering
102
+ # `#<Class:Liquid::Utils>` raises ArgumentError). A coverage report
103
+ # must never crash the host suite over that, so on failure rebuild the
104
+ # singleton wrapper from `Module#name` via bound methods (which cannot
105
+ # be shadowed), falling back to the address form, which
106
+ # ADDRESS_PATTERN then normalizes. See issue #1236.
107
+ def class_display_name(klass)
108
+ klass.to_s
109
+ rescue StandardError
110
+ singleton_wrapper_name(klass) || Object.instance_method(:to_s).bind_call(klass)
111
+ end
112
+
113
+ def singleton_wrapper_name(klass)
114
+ return nil unless klass.is_a?(Class) && klass.singleton_class?
115
+
116
+ attached = klass.attached_object
117
+ # simplecov:disable branch — CRuby only reaches the rescue via a
118
+ # Module/Class attached object (instance singletons render from the
119
+ # class name chain without calling user inspect), so the non-Module
120
+ # arm is defensive.
121
+ name = Module.instance_method(:name).bind_call(attached) if attached.is_a?(Module)
122
+ # simplecov:enable branch
123
+ name && "#<Class:#{name}>"
124
+ end
125
+
94
126
  # Ruby's eval coverage records a fresh set of branch entries for every
95
127
  # COMPILE of an eval'd string: a template rendered through multiple view
96
128
  # classes (e.g. hanami-view compiles each template once per view) yields
@@ -15,10 +15,28 @@ module SimpleCov
15
15
 
16
16
  # Tests use the real data structures (except for integration tests)
17
17
  # so no need to put them through here.
18
+ #
19
+ # String parses are memoized: `Combine::BranchesCombiner` and
20
+ # `Combine::MethodsCombiner` derive a merge identity from every key of
21
+ # both sides on every pairwise merge, so collating N resultsets parses
22
+ # each key string N-1 times — and Ripper dominates the wall time of a
23
+ # large collate.
24
+ # Key strings repeat across folds and within report building, while the
25
+ # set of unique keys is bounded by the project's branch and method
26
+ # count, so a permanent cache stays small. Cached arrays are frozen
27
+ # element-wise (String class names included, not just the tuple):
28
+ # every caller destructures without mutating, and sharing one array
29
+ # across callers must stay that way. The cache key needs no such
30
+ # care — `Hash#[]=` dups and freezes String keys on its own.
18
31
  def call(structure)
19
32
  return structure if structure.is_a?(Array)
20
33
 
21
- parse_array_string(structure.to_s)
34
+ string = structure.to_s
35
+ parse_cache[string] ||= parse_array_string(string).each(&:freeze).freeze
36
+ end
37
+
38
+ def parse_cache
39
+ @parse_cache ||= {} #: Hash[String, untyped]
22
40
  end
23
41
 
24
42
  # Parse a string like '[:if, 0, 3, 4, 3, 21]' or
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- VERSION = "1.0.2"
4
+ VERSION = "1.0.3"
5
5
  end
data/sig/simplecov.rbs CHANGED
@@ -1217,13 +1217,18 @@ end
1217
1217
  module SimpleCov
1218
1218
  # Namespace for result formatters. A formatter is any class whose
1219
1219
  # instances respond to `#format(result)` and that can be instantiated
1220
- # with no arguments; wire one up via `SimpleCov.formatter=` or
1220
+ # with no arguments, or a ready-built instance responding to
1221
+ # `#format(result)`; wire one up via `SimpleCov.formatter=` or
1221
1222
  # `SimpleCov.formatters=`.
1222
1223
  module Formatter
1223
1224
  # Structural interface for custom formatter instances.
1224
1225
  interface _Formatter
1225
1226
  def format: (SimpleCov::Result result) -> untyped
1226
1227
  end
1228
+
1229
+ # Normalizes a configured formatter — class or ready-built
1230
+ # instance — into an instance. See #1240.
1231
+ def self.instance_for: (untyped formatter) -> untyped
1227
1232
  end
1228
1233
  end
1229
1234
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simplecov
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erik Berlin
@@ -154,9 +154,9 @@ licenses:
154
154
  metadata:
155
155
  bug_tracker_uri: https://github.com/simplecov-ruby/simplecov/issues
156
156
  changelog_uri: https://github.com/simplecov-ruby/simplecov/blob/main/CHANGELOG.md
157
- documentation_uri: https://www.rubydoc.info/gems/simplecov/1.0.2
157
+ documentation_uri: https://www.rubydoc.info/gems/simplecov/1.0.3
158
158
  mailing_list_uri: https://groups.google.com/forum/#!forum/simplecov
159
- source_code_uri: https://github.com/simplecov-ruby/simplecov/tree/v1.0.2
159
+ source_code_uri: https://github.com/simplecov-ruby/simplecov/tree/v1.0.3
160
160
  rubygems_mfa_required: 'true'
161
161
  rdoc_options: []
162
162
  require_paths:
@@ -172,7 +172,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
172
172
  - !ruby/object:Gem::Version
173
173
  version: '0'
174
174
  requirements: []
175
- rubygems_version: 4.0.16
175
+ rubygems_version: 4.0.17
176
176
  specification_version: 4
177
177
  summary: Code coverage for Ruby
178
178
  test_files: []