mutineer 0.9.0 → 0.9.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a8687c96cedac16e86197f3639803487657512509c45a1969449eb95b3c4b65c
4
- data.tar.gz: 59588c405f16476b984fefbda003299f8373687cacd40c4569ea72edb866d0a9
3
+ metadata.gz: 0d61c927b33961995a0d38b691cdf0b34a6e9041213980c0cd7a3a01f457f875
4
+ data.tar.gz: 8e4f32e923344837ab664805a3099a9e67d405bbe7b63d83986d1af0b488ead5
5
5
  SHA512:
6
- metadata.gz: edf0bff64186f9719f4de85a0672e7b6614d161aa02b87b63600ebdb2e82e744ef706fd0db29a990d159be3ed422a35b9e3fd7d505595ca585e483fe9ababce1
7
- data.tar.gz: 1e41589e16979a227b9a479504574d939ff5f91077535723e1b3cfc87e53fa20475e1ba1c09dce910b9644bc002f631ac5001d60737a98a9940ab06ea7dbb023
6
+ metadata.gz: 5ea750d511b52ef0f9d320c39ef0b4a3b51189116c09811b3bf4f2d5752b026d1ddc62411387a261f6f60ba73e23e2b76af1e553a745bd63f9a3d56a0cd068cb
7
+ data.tar.gz: 6dc10950ea34998e459e5007095bd43df87a633a4ca18e19afee1812a8fed154e086f3ae3a7b808bd84ba93336bb61208195d001efd846fea367502a23167ef2
data/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@ All notable changes to this project are documented here. The format is based on
4
4
  [Keep a Changelog](https://keepachangelog.com/), and this project adheres to
5
5
  [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.9.1] - 2026-07-01
8
+
9
+ ### Fixed
10
+ - **Per-method uncapturable granularity** (#25) — the `:uncapturable` taint was
11
+ whole-file, so a method reachable only by a *failed* capture in an otherwise-
12
+ covered file was mislabeled `no_coverage`. It's now attributed per method (by
13
+ the method's body coverage), so only the affected method is tainted. Fully-
14
+ failed files are unchanged.
15
+
7
16
  ## [0.9.0] - 2026-06-30
8
17
 
9
18
  ### Added
@@ -175,6 +184,7 @@ Rails hardening + CI batch (issues #8–#13), all verified Rails-free.
175
184
  - `.mutineer.yml` configuration (CLI > config > default precedence).
176
185
  - Byte-correct source handling for multibyte (UTF-8) sources.
177
186
 
187
+ [0.9.1]: https://github.com/davidteren/mutineer/releases/tag/v0.9.1
178
188
  [0.9.0]: https://github.com/davidteren/mutineer/releases/tag/v0.9.0
179
189
  [0.8.0]: https://github.com/davidteren/mutineer/releases/tag/v0.8.0
180
190
  [0.7.1]: https://github.com/davidteren/mutineer/releases/tag/v0.7.1
@@ -87,6 +87,27 @@ module Mutineer
87
87
  failed_test_targets.include?(File.basename(rel, ".rb"))
88
88
  end
89
89
 
90
+ # #25: per-METHOD taint. A mutant on a line whose enclosing method got zero
91
+ # successful coverage, in a file a failed sibling test targets, is
92
+ # :uncapturable (the capture that would have covered it errored) — NOT a
93
+ # genuine gap. A method with any covered line means its uncovered lines are a
94
+ # real :no_coverage. A failed capture emits no coverage, so per-line intent is
95
+ # unknowable; method-range + successful coverage is the finest derivable signal.
96
+ # Fully-failed files behave exactly as uncapturable_source? did (every method
97
+ # range has zero coverage), so #8/#9/#19 behavior is unchanged.
98
+ #
99
+ # @param file [String] source file path.
100
+ # @param line_range [Range] 1-based enclosing-method line range.
101
+ # @return [Boolean]
102
+ def method_uncapturable?(file, line_range)
103
+ return false if @failed_test_files.empty?
104
+
105
+ rel = relativize(absolute(file))
106
+ return false unless failed_test_targets.include?(File.basename(rel, ".rb"))
107
+
108
+ line_range.none? { |ln| @map.key?("#{rel}:#{ln}") }
109
+ end
110
+
90
111
  private
91
112
 
92
113
  # Source rel-paths that received coverage from any successful capture.
@@ -259,10 +259,18 @@ module Mutineer
259
259
  # covering test files run in the child.
260
260
  line = source.byteslice(0, mutation.start_offset).count("\n") + 1
261
261
  chosen = coverage_map.tests_for(source_file, line)
262
- # #9: distinguish a genuine coverage gap from a line whose would-be test
262
+ # #9/#25: distinguish a genuine coverage gap from a line whose would-be test
263
263
  # errored during capture (coverage lost) — the latter is :uncapturable.
264
+ # #25: taint per-METHOD (the mutant's enclosing def range), not whole-file,
265
+ # so a covered method's uncovered line stays :no_coverage while a method
266
+ # reachable only by a failed capture is :uncapturable.
264
267
  if chosen.empty?
265
- return coverage_map.uncapturable_source?(source_file) ? Result.uncapturable : Result.no_coverage
268
+ # Use the method BODY range, not the whole def: the `def`/`end` lines are
269
+ # "covered" at class-load even when the body never runs, which would mask
270
+ # an uncovered method. body_loc is the body statements' span.
271
+ loc = subject&.body_loc
272
+ range = loc ? (loc.start_line..loc.end_line) : (line..line)
273
+ return coverage_map.method_uncapturable?(source_file, range) ? Result.uncapturable : Result.no_coverage
266
274
  end
267
275
 
268
276
  abs_tests = chosen.map { |t| File.expand_path(t, coverage_map.project_root) }
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Mutineer
4
4
  # Current Mutineer release version.
5
- VERSION = "0.9.0"
5
+ VERSION = "0.9.1"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mutineer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Teren