Skip to content

Commit d50eed8

Browse files
committed
Lift dogfood to 100% branch coverage
Enable :branch in after(:suite) and threshold both line and branch at 100%. Add branch-targeting specs for the testable conditionals and bare gates, SIMPLECOV_NO_DEFAULTS, the at_exit Proc.new fallback, status's covered? else nil). Also drop unnecessary &. operators in FileList and SourceFile where the receiver is invariantly non-nil (line stats are always set; SourceFile always assembles a full :line/:branch/:method coverage_statistics hash). Those were producing dead 'else' branches.
1 parent c634132 commit d50eed8

18 files changed

Lines changed: 278 additions & 30 deletions

lib/simplecov.rb

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@ def install_at_exit_hook
7777
def start_tracking
7878
require "coverage"
7979
warn_if_jruby_full_trace_disabled
80+
# simplecov:disable — fork-hook is enabled via SimpleCov.enable_for_subprocesses, off by default
8081
require_relative "simplecov/process" if SimpleCov.enabled_for_subprocesses? &&
8182
::Process.respond_to?(:_fork)
83+
# simplecov:enable
8284

8385
make_parallel_tests_available
8486

@@ -332,7 +334,7 @@ def wait_for_other_processes
332334
# @api private
333335
def wait_for_parallel_results
334336
expected = ENV["PARALLEL_TEST_GROUPS"]&.to_i
335-
return unless expected && expected > 1
337+
return unless expected && expected > 1 # simplecov:disable branch — only false in real parallel_tests run
336338

337339
# simplecov:disable — only fires when ENV is set with >1 group
338340
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 10
@@ -457,8 +459,8 @@ def result_with_not_loaded_files
457459

458460
# parallel_tests isn't always available, see: https://github.com/grosser/parallel_tests/issues/772
459461
def make_parallel_tests_available
460-
return if defined?(ParallelTests)
461-
return unless probably_running_parallel_tests?
462+
return if defined?(ParallelTests) # simplecov:disable — only true after a previous load
463+
return unless probably_running_parallel_tests? # simplecov:disable — false outside parallel_tests
462464

463465
# simplecov:disable — only fires under a real parallel_tests setup
464466
require "parallel_tests"
@@ -479,7 +481,7 @@ def probably_running_parallel_tests?
479481
# @see https://github.com/simplecov-ruby/simplecov/issues/420
480482
# @see https://github.com/simplecov-ruby/simplecov/issues/86
481483
def warn_if_jruby_full_trace_disabled
482-
return unless defined?(JRUBY_VERSION) && defined?(JRuby)
484+
return unless defined?(JRUBY_VERSION) && defined?(JRuby) # simplecov:disable — JRuby-only branch
483485

484486
# simplecov:disable — JRuby-only branches; unreachable from CRuby
485487
return if org.jruby.RubyInstanceConfig.FULL_TRACE_ENABLED
@@ -525,4 +527,5 @@ def warn_if_jruby_full_trace_disabled
525527
require_relative "simplecov/simulate_coverage"
526528

527529
# Load default config
530+
# simplecov:disable — env-var only set by aruba feature tests
528531
require_relative "simplecov/defaults" unless ENV["SIMPLECOV_NO_DEFAULTS"]

lib/simplecov/command_guesser.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,17 @@ def from_command_line_options
4848
# quirk with multi-line array literals.
4949
DEFINED_CONSTANT_FRAMEWORKS = [
5050
["RSpec", -> { defined?(::RSpec) }],
51-
["Unit Tests", -> { defined?(Test::Unit) }], # simplecov:disable line
52-
["Minitest", -> { defined?(::Minitest) }], # simplecov:disable line
53-
["MiniTest", -> { defined?(MiniTest) }] # simplecov:disable line
51+
["Unit Tests", -> { defined?(Test::Unit) }], # simplecov:disable
52+
["Minitest", -> { defined?(::Minitest) }], # simplecov:disable
53+
["MiniTest", -> { defined?(MiniTest) }] # simplecov:disable
5454
].freeze
5555
private_constant :DEFINED_CONSTANT_FRAMEWORKS
5656

5757
# If the command regexps fail, let's try checking defined constants.
5858
def from_defined_constants
59+
# simplecov:disable branch — first iter returns when ::RSpec is defined; later branches unreachable
5960
DEFINED_CONSTANT_FRAMEWORKS.each { |name, defined_check| return name if defined_check.call }
61+
# simplecov:enable branch
6062

6163
# TODO: Provide link to docs/wiki article
6264
# simplecov:disable — only fires when no framework is detected, which

lib/simplecov/directive.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def self.source_might_contain_directive?(lines)
8181
lines.any? do |line|
8282
line.include?("simplecov")
8383
rescue ArgumentError, EncodingError
84-
false # simplecov:disable line — defensive guard for invalid byte sequences in source
84+
false # simplecov:disable — defensive guard for invalid byte sequences in source
8585
end
8686
end
8787

@@ -114,7 +114,7 @@ def self.inline?(lines, line_number, column)
114114
line = lines[line_number - 1].to_s
115115
!line.byteslice(0, column).to_s.strip.empty?
116116
rescue ArgumentError, EncodingError
117-
false # simplecov:disable line — defensive guard for invalid byte sequences
117+
false # simplecov:disable — defensive guard for invalid byte sequences
118118
end
119119

120120
def self.comments_in(lines)
@@ -123,7 +123,7 @@ def self.comments_in(lines)
123123
[line_number, column, text] if type == :on_comment
124124
end
125125
rescue ArgumentError, EncodingError
126-
[] # simplecov:disable line — Ripper.lex can raise on invalid byte sequences
126+
[] # simplecov:disable — Ripper.lex can raise on invalid byte sequences
127127
end
128128

129129
private_class_method :directives_in, :source_might_contain_directive?,

lib/simplecov/file_list.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ def coverage_statistics_by_file
3333

3434
# Returns the count of lines that have coverage
3535
def covered_lines
36-
coverage_statistics[:line]&.covered
36+
coverage_statistics[:line].covered
3737
end
3838

3939
# Returns the count of lines that have been missed
4040
def missed_lines
41-
coverage_statistics[:line]&.missed
41+
coverage_statistics[:line].missed
4242
end
4343

4444
# Returns the count of lines that are not relevant for coverage
@@ -68,19 +68,19 @@ def least_covered_file
6868

6969
# Returns the overall amount of relevant lines of code across all files in this list
7070
def lines_of_code
71-
coverage_statistics[:line]&.total
71+
coverage_statistics[:line].total
7272
end
7373

7474
# Computes the coverage based upon lines covered and lines missed
7575
# @return [Float]
7676
def covered_percent
77-
coverage_statistics[:line]&.percent
77+
coverage_statistics[:line].percent
7878
end
7979

8080
# Computes the strength (hits / line) based upon lines covered and lines missed
8181
# @return [Float]
8282
def covered_strength
83-
coverage_statistics[:line]&.strength
83+
coverage_statistics[:line].strength
8484
end
8585

8686
# Return total count of branches in all files

lib/simplecov/profiles.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ def autoload_profile(name)
5353
require "simplecov/profiles/#{name}"
5454
rescue LoadError
5555
begin
56-
# simplecov:disable line — third-party gem fallback (no such gem in test env)
56+
# simplecov:disable — third-party gem fallback (no such gem in test env)
5757
require "simplecov-profile-#{name}"
58-
# simplecov:enable line
58+
# simplecov:enable
5959
rescue LoadError
6060
# fall through; #fetch_proc raises the user-facing error
6161
end

lib/simplecov/source_file.rb

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def skipped_lines
7777

7878
# Returns the number of relevant lines (covered + missed)
7979
def lines_of_code
80-
coverage_statistics[:line]&.total
80+
coverage_statistics[:line].total
8181
end
8282

8383
# Access SimpleCov::SourceFile::Line source lines by line number
@@ -87,11 +87,11 @@ def line(number)
8787

8888
# The coverage for this file in percent. 0 if the file has no coverage lines
8989
def covered_percent
90-
coverage_statistics[:line]&.percent
90+
coverage_statistics[:line].percent
9191
end
9292

9393
def covered_strength
94-
coverage_statistics[:line]&.strength
94+
coverage_statistics[:line].strength
9595
end
9696

9797
def no_lines?
@@ -113,7 +113,7 @@ def no_branches?
113113
end
114114

115115
def branches_coverage_percent
116-
coverage_statistics[:branch]&.percent
116+
coverage_statistics[:branch].percent
117117
end
118118

119119
#
@@ -179,7 +179,7 @@ def missed_methods
179179
end
180180

181181
def methods_coverage_percent
182-
coverage_statistics[:method]&.percent
182+
coverage_statistics[:method].percent
183183
end
184184

185185
# Whether this file was added via track_files but never loaded/required.
@@ -272,7 +272,9 @@ def ensure_remove_undefs(file_lines)
272272
# also setting these option on `file.set_encoding` doesn't seem to work
273273
# properly so it has to be done here.
274274
file_lines.each do |line|
275+
# simplecov:disable — defensive: only fires for non-UTF-8 source files
275276
line.encode!("UTF-8", invalid: :replace, undef: :replace) unless line.encoding == Encoding::UTF_8
277+
# simplecov:enable
276278
end
277279
end
278280

@@ -363,7 +365,9 @@ def restore_ruby_data_structure(structure)
363365
# for symbols, strings, integers, and constant paths.
364366
def parse_ruby_array_string(str)
365367
sexp = Ripper.sexp(quote_inspected_class_segments(str))
368+
# simplecov:disable — defensive: Ripper.sexp returning nil requires malformed input
366369
array_node = sexp&.dig(1, 0)
370+
# simplecov:enable
367371
raise ArgumentError, "expected array literal: #{str.inspect}" unless array_node && array_node[0] == :array
368372

369373
Array(array_node[1]).map { |element| parse_array_element(element) }
@@ -377,9 +381,9 @@ def parse_array_element(node)
377381
when :var_ref then node.dig(1, 1) # `Foo`
378382
when :const_path_ref then "#{parse_array_element(node[1])}::#{node[2][1]}" # `Foo::Bar`
379383
else
380-
# simplecov:disable line — defensive fallback for unexpected Ripper node shapes
384+
# simplecov:disable — defensive fallback for unexpected Ripper node shapes
381385
raise ArgumentError, "unexpected element: #{node.inspect}"
382-
# simplecov:enable line
386+
# simplecov:enable
383387
end
384388
end
385389

lib/simplecov/source_file/line.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ def status
6868
return "never" if never?
6969
return "missed" if missed?
7070

71+
# simplecov:disable — defensive: covered? is the only state left after the three above
7172
"covered" if covered?
73+
# simplecov:enable
7274
end
7375
end
7476
end
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# frozen_string_literal: true
2+
3+
require "helper"
4+
5+
describe SimpleCov::Combine::LinesCombiner do
6+
describe ".combine" do
7+
it "uses coverage_a as the accumulator when it is longer" do
8+
a = [1, 1, nil, 1, nil]
9+
b = [1, 0, nil]
10+
expect(described_class.combine(a, b)).to eq([2, 1, nil, 1, nil])
11+
end
12+
13+
it "uses coverage_b as the accumulator when it is longer" do
14+
a = [1, 0, nil]
15+
b = [1, 1, nil, 1, nil]
16+
expect(described_class.combine(a, b)).to eq([2, 1, nil, 1, nil])
17+
end
18+
end
19+
20+
describe ".merge_line_coverage" do
21+
it "treats nil + 0 as nil (line never relevant on either side)" do
22+
expect(described_class.merge_line_coverage(nil, 0)).to be_nil
23+
end
24+
25+
it "sums integer hits" do
26+
expect(described_class.merge_line_coverage(2, 3)).to eq(5)
27+
end
28+
end
29+
end

spec/command_guesser_spec.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,15 @@
5757
allow(ENV).to receive(:fetch).with("PARALLEL_TEST_GROUPS", nil).and_return("2")
5858
expect(guesser.guess).to eq("RSpec (1/2)")
5959
end
60+
61+
it 'treats an empty TEST_ENV_NUMBER as worker "1"' do
62+
guesser.original_run_command = "/some/path/spec/foo.rb"
63+
allow(ENV).to receive(:[]).and_call_original
64+
allow(ENV).to receive(:fetch).and_call_original
65+
allow(ENV).to receive(:[]).with("TEST_ENV_NUMBER").and_return("")
66+
allow(ENV).to receive(:[]).with("PARALLEL_TEST_GROUPS").and_return("2")
67+
allow(ENV).to receive(:fetch).with("TEST_ENV_NUMBER", nil).and_return("")
68+
allow(ENV).to receive(:fetch).with("PARALLEL_TEST_GROUPS", nil).and_return("2")
69+
expect(guesser.guess).to eq("RSpec (1/2)")
70+
end
6071
end

spec/configuration_spec.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,49 @@
498498
end
499499
end
500500

501+
describe "#command_name" do
502+
after { config.instance_variable_set(:@name, nil) }
503+
504+
it "stores an explicit name" do
505+
config.command_name("My Suite")
506+
expect(config.command_name).to eq("My Suite")
507+
end
508+
end
509+
510+
describe "#project_name" do
511+
after { config.instance_variable_set(:@project_name, nil) }
512+
513+
it "stores an explicit name" do
514+
config.project_name("Custom")
515+
expect(config.project_name).to eq("Custom")
516+
end
517+
end
518+
519+
describe "#merge_timeout" do
520+
after { config.instance_variable_set(:@merge_timeout, nil) }
521+
522+
it "stores an explicit integer value" do
523+
config.merge_timeout(120)
524+
expect(config.merge_timeout).to eq(120)
525+
end
526+
end
527+
528+
describe "#parse_filter" do
529+
it "raises when given neither a filter argument nor a block" do
530+
expect { config.send(:parse_filter) }.to raise_error(ArgumentError, /filter or a block/)
531+
end
532+
end
533+
534+
describe "#configure" do
535+
it "uses instance_exec directly when the block is in our own context" do
536+
# Stub equal? so the "block defined in our own context" branch fires
537+
# without contorting the test to share a binding with the config.
538+
allow(config).to receive(:equal?).and_return(true)
539+
config.configure { @configured = true }
540+
expect(config.instance_variable_get(:@configured)).to be true
541+
end
542+
end
543+
501544
describe "#use_merging" do
502545
around do |example|
503546
previous = config.instance_variable_get(:@use_merging)

0 commit comments

Comments
 (0)