Module: SimpleCov

Extended by:
Configuration
Defined in:
lib/simplecov.rb,
lib/simplecov/cli.rb,
lib/simplecov/color.rb,
lib/simplecov/filter.rb,
lib/simplecov/result.rb,
lib/simplecov/cli/run.rb,
lib/simplecov/combine.rb,
lib/simplecov/process.rb,
lib/simplecov/version.rb,
lib/simplecov/cli/diff.rb,
lib/simplecov/cli/open.rb,
lib/simplecov/last_run.rb,
lib/simplecov/profiles.rb,
lib/simplecov/cli/clean.rb,
lib/simplecov/cli/merge.rb,
lib/simplecov/cli/serve.rb,
lib/simplecov/directive.rb,
lib/simplecov/file_list.rb,
lib/simplecov/formatter.rb,
lib/simplecov/cli/report.rb,
lib/simplecov/exit_codes.rb,
lib/simplecov/cli/dotfile.rb,
lib/simplecov/source_file.rb,
lib/simplecov/cli/coverage.rb,
lib/simplecov/cli/uncovered.rb,
lib/simplecov/configuration.rb,
lib/simplecov/exit_handling.rb,
lib/simplecov/result_merger.rb,
lib/simplecov/formatter/base.rb,
lib/simplecov/result_adapter.rb,
lib/simplecov/command_guesser.rb,
lib/simplecov/lines_classifier.rb,
lib/simplecov/source_file/line.rb,
lib/simplecov/parallel_adapters.rb,
lib/simplecov/result_processing.rb,
lib/simplecov/simulate_coverage.rb,
lib/simplecov/source_file/branch.rb,
lib/simplecov/source_file/method.rb,
lib/simplecov/coverage_statistics.rb,
lib/simplecov/coverage_violations.rb,
lib/simplecov/configuration/filters.rb,
lib/simplecov/configuration/merging.rb,
lib/simplecov/parallel_coordination.rb,
lib/simplecov/combine/files_combiner.rb,
lib/simplecov/combine/lines_combiner.rb,
lib/simplecov/configuration/coverage.rb,
lib/simplecov/parallel_adapters/base.rb,
lib/simplecov/source_file/statistics.rb,
lib/simplecov/source_file/skip_chunks.rb,
lib/simplecov/useless_results_remover.rb,
lib/simplecov/combine/methods_combiner.rb,
lib/simplecov/combine/results_combiner.rb,
lib/simplecov/configuration/formatting.rb,
lib/simplecov/configuration/thresholds.rb,
lib/simplecov/formatter/html_formatter.rb,
lib/simplecov/formatter/json_formatter.rb,
lib/simplecov/source_file/line_builder.rb,
lib/simplecov/combine/branches_combiner.rb,
lib/simplecov/formatter/multi_formatter.rb,
lib/simplecov/parallel_adapters/generic.rb,
lib/simplecov/source_file/source_loader.rb,
lib/simplecov/static_coverage_extractor.rb,
lib/simplecov/formatter/simple_formatter.rb,
lib/simplecov/result/source_file_builder.rb,
lib/simplecov/source_file/branch_builder.rb,
lib/simplecov/source_file/method_builder.rb,
lib/simplecov/source_file/builder_context.rb,
lib/simplecov/result_merger/resultset_file.rb,
lib/simplecov/source_file/ruby_data_parser.rb,
lib/simplecov/configuration/ignored_entries.rb,
lib/simplecov/exit_codes/exit_code_handling.rb,
lib/simplecov/result_merger/resultset_store.rb,
lib/simplecov/configuration/coverage_criteria.rb,
lib/simplecov/parallel_adapters/parallel_tests.rb,
lib/simplecov/static_coverage_extractor/visitor.rb,
lib/simplecov/result_merger/legacy_format_adapter.rb,
lib/simplecov/result/missing_source_files_reporter.rb,
lib/simplecov/exit_codes/maximum_coverage_drop_check.rb,
lib/simplecov/exit_codes/maximum_overall_coverage_check.rb,
lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb,
lib/simplecov/exit_codes/minimum_overall_coverage_check.rb,
lib/simplecov/formatter/json_formatter/errors_formatter.rb,
lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb,
lib/simplecov/formatter/json_formatter/result_hash_formatter.rb,
lib/simplecov/formatter/json_formatter/source_file_formatter.rb

Overview

simplecov:disable Methods below only fire under a parallel test runner; not reachable from a single-process rspec run. Cucumber’s test_projects exercise the parallel_tests integration end-to-end in subprocesses, but those subprocesses don’t merge their Coverage data back into the parent this dogfood report measures.

Defined Under Namespace

Modules: CLI, Color, Combine, CommandGuesser, Configuration, CoverageViolations, ExitCodes, Formatter, LastRun, ParallelAdapters, ProcessForkHook, ResultMerger, SimulateCoverage, StaticCoverageExtractor, UselessResultsRemover Classes: ArrayFilter, BlockFilter, ConfigurationError, CoverageLimits, CoverageStatistics, Directive, FileList, Filter, GlobFilter, LinesClassifier, Profiles, RegexFilter, Result, ResultAdapter, SourceFile, StringFilter

Constant Summary collapse

CRITERION_TO_RUBY_COVERAGE =
{
  branch: :branches,
  line: :lines,
  method: :methods,
  oneshot_line: :oneshot_lines
}.freeze
VERSION =
"1.0.0.rc1"

Constants included from Configuration

Configuration::COVERAGE_THRESHOLD_OPTIONS, Configuration::DEFAULT_COVERAGE_CRITERION, Configuration::IGNORABLE_BRANCH_TYPES, Configuration::IGNORABLE_METHOD_TYPES, Configuration::ONESHOT_LINE_COVERAGE_CRITERION, Configuration::SUPPORTED_COVERAGE_CRITERIA

Class Attribute Summary collapse

Attributes included from Configuration

#filters, #formatter, #groups, #print_error_status

Class Method Summary collapse

Methods included from Configuration

active_session?, add_filter, add_group, at_exit, at_fork, branch_coverage?, branch_coverage_supported?, clear_coverage_criteria, clear_filters, color, command_name, configure, cover, cover_filters, cover_globs, coverage, coverage_criteria, coverage_criterion_enabled?, coverage_criterion_supported?, coverage_dir, coverage_for_eval_enabled?, coverage_for_eval_supported?, coverage_path, current_nocov_token, disable_coverage, enable_coverage, enable_coverage_for_eval, enable_for_subprocesses, enabled_for_subprocesses?, expected_coverage, formatters, formatters=, group, ignore_branches, ignore_methods, ignored_branch?, ignored_branches, ignored_method?, ignored_methods, maximum_coverage, maximum_coverage_drop, merge_subprocesses, merge_timeout, merging, method_coverage?, method_coverage_supported?, minimum_coverage, minimum_coverage_by_file, minimum_coverage_by_file_overrides, minimum_coverage_by_group, no_default_skips, nocov_token, parallel_tests, primary_coverage, print_errors, profiles, project_name, raise_on_invalid_coverage, refuse_coverage_drop, remove_filter, root, skip, source_in_json, track_files, track_files_replacement_hint, tracked_files, use_merging, validate_coverage_criteria!

Class Attribute Details

.external_at_exitObject

Should we take care of at_exit behavior or something else? Used by the minitest plugin. See lib/minitest/simplecov_plugin.rb.



26
27
28
# File 'lib/simplecov.rb', line 26

def external_at_exit
  @external_at_exit
end

.pidObject

Returns the value of attribute pid.



19
20
21
# File 'lib/simplecov.rb', line 19

def pid
  @pid
end

.process_start_timeObject

When this process started tracking coverage. Captured by SimpleCov.start so JSONFormatter can detect when an existing coverage.json was written by a sibling process running concurrently.



23
24
25
# File 'lib/simplecov.rb', line 23

def process_start_time
  @process_start_time
end

Class Method Details

.at_exit_behaviorObject



21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/simplecov/exit_handling.rb', line 21

def at_exit_behavior
  # If we are in a different process than called start, don't interfere.
  return if SimpleCov.pid != Process.pid

  # If Coverage is no longer running (e.g. someone manually stopped it
  # or a test consumed the result) then don't run exit tasks.
  return unless Coverage.running?

  # Stand down when we'd only clobber a fresher report. See
  # `defer_to_existing_report?` and issue #581.
  return if defer_to_existing_report?

  SimpleCov.run_exit_tasks!
end

.clear_resultObject

Clear out the previously cached .result. Primarily useful in testing.



87
88
89
# File 'lib/simplecov/result_processing.rb', line 87

def clear_result
  @result = nil
end

.collate(result_filenames, profile = nil, ignore_timeout: true, &block) ⇒ Object

Collate a series of SimpleCov result files into a single SimpleCov output.

See README for usage. By default ‘collate` ignores the merge_timeout so all results in all files specified will be merged. Pass `ignore_timeout: false` to honor it.

Raises:

  • (ArgumentError)


16
17
18
19
20
21
22
23
24
25
# File 'lib/simplecov/result_processing.rb', line 16

def collate(result_filenames, profile = nil, ignore_timeout: true, &block)
  raise ArgumentError, "There are no reports to be merged" if result_filenames.empty?

  initial_setup(profile, &block)

  # Use the ResultMerger to produce a single, merged result, ready to use.
  @result = ResultMerger.merge_and_store(*result_filenames, ignore_timeout: ignore_timeout)

  run_exit_tasks!
end

.coverage_statistics_key(criterion) ⇒ Object

‘:oneshot_line` data is folded into the `:line` bucket of `coverage_statistics` by `ResultAdapter`, so use `:line` to look up stats for either criterion.



31
32
33
# File 'lib/simplecov.rb', line 31

def coverage_statistics_key(criterion)
  criterion == :oneshot_line ? :line : criterion
end

.defer_to_existing_report?Boolean

Returns true when our process has no coverage data to contribute (after the resultset merge) and a newer report already exists on disk. Typically fires when ‘SimpleCov.start` ran in a parent process — e.g. a Rakefile or Rails’ ‘Bundler.require` — that shelled out to the test runner. See issue #581.

Returns:

  • (Boolean)


41
42
43
44
45
46
47
48
# File 'lib/simplecov/exit_handling.rb', line 41

def defer_to_existing_report?
  return false unless existing_report_newer_than_us?

  res = result
  empty = res.nil? || res.files.empty?
  warn_about_deferred_report if empty
  empty
end

.existing_report_newer_than_us?Boolean

Returns:

  • (Boolean)


50
51
52
53
54
55
# File 'lib/simplecov/exit_handling.rb', line 50

def existing_report_newer_than_us?
  return false unless process_start_time

  last_run_path = File.join(coverage_path, ".last_run.json")
  File.exist?(last_run_path) && File.mtime(last_run_path) > process_start_time
end

.exit_and_report_previous_error(exit_status) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



99
100
101
102
103
104
105
106
107
# File 'lib/simplecov/exit_handling.rb', line 99

def exit_and_report_previous_error(exit_status)
  if print_errors
    warn SimpleCov::Color.colorize(
      "Stopped processing SimpleCov as a previous error not related to SimpleCov has been detected",
      :yellow
    )
  end
  Kernel.exit(exit_status)
end

.exit_status_from_exceptionObject



80
81
82
83
84
85
86
87
88
89
# File 'lib/simplecov/exit_handling.rb', line 80

def exit_status_from_exception
  @exit_exception = $ERROR_INFO
  return nil unless @exit_exception

  if @exit_exception.is_a?(SystemExit)
    @exit_exception.status
  else
    SimpleCov::ExitCodes::EXCEPTION
  end
end

.external_at_exit?Boolean

Coerce to a proper boolean so rspec-mocks 4’s predicate matcher (‘expect(…).not_to be_external_at_exit`) accepts the result.

Returns:

  • (Boolean)


37
38
39
# File 'lib/simplecov.rb', line 37

def external_at_exit?
  !!@external_at_exit
end

.filtered(files) ⇒ Object

Applies the configured filters to the given array of SimpleCov::SourceFile items



54
55
56
57
58
59
60
# File 'lib/simplecov/result_processing.rb', line 54

def filtered(files)
  result = files.clone
  filters.each do |filter|
    result = result.reject { |source_file| filter.matches?(source_file) }
  end
  SimpleCov::FileList.new result
end

.final_result_process?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


23
24
25
26
27
28
# File 'lib/simplecov/parallel_coordination.rb', line 23

def final_result_process?
  adapter = SimpleCov::ParallelAdapters.current
  return true unless adapter

  adapter.first_worker?
end

.grouped(files, groups: SimpleCov.groups) ⇒ Object

Bin the given source files by group filter. ‘groups:` defaults to `SimpleCov.groups`; pass a Hash explicitly to bin against a different group config (e.g., the snapshot a Result captured at construction). Files matched by no group fall into the implicit “Ungrouped” bucket.



67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/simplecov/result_processing.rb', line 67

def grouped(files, groups: SimpleCov.groups)
  return {} if groups.empty?

  grouped = groups.transform_values do |filter|
    SimpleCov::FileList.new(files.select { |source_file| filter.matches?(source_file) })
  end

  in_group  = grouped_file_set(grouped)
  ungrouped = files.reject { |source_file| in_group.include?(source_file) }
  grouped["Ungrouped"] = SimpleCov::FileList.new(ungrouped) if ungrouped.any?

  grouped
end

.install_at_exit_hookObject

Install the at_exit hook that formats results and runs exit-code checks. ‘SimpleCov.start` calls this automatically. Idempotent —safe to call multiple times. Callers that drive the formatting pipeline themselves (e.g., dogfood test setups) can skip it by using `start_tracking` directly instead of `start`.



91
92
93
94
95
96
97
98
99
100
101
# File 'lib/simplecov.rb', line 91

def install_at_exit_hook
  return if @at_exit_hook_installed

  @at_exit_hook_installed = true
  defer_to_minitest_after_run if minitest_autorun_pending?
  Kernel.at_exit do
    next if SimpleCov.external_at_exit?

    SimpleCov.at_exit_behavior
  end
end

.load_profile(name) ⇒ Object

Applies the profile of given name on SimpleCov configuration



82
83
84
# File 'lib/simplecov/result_processing.rb', line 82

def load_profile(name)
  profiles.load(name)
end

.parallel_results_complete?Boolean

before the wait deadline. Defaults to true outside a parallel run (when ‘wait_for_other_processes` is a no-op).

Returns:

  • (Boolean)


53
54
55
# File 'lib/simplecov/parallel_coordination.rb', line 53

def parallel_results_complete?
  defined?(@parallel_results_complete) ? @parallel_results_complete : true
end

.parallel_wait_timed_out?(deadline, expected, seen) ⇒ Boolean

the first timeout so the user knows the merged total is partial.

Returns:

  • (Boolean)


75
76
77
78
79
80
# File 'lib/simplecov/parallel_coordination.rb', line 75

def parallel_wait_timed_out?(deadline, expected, seen)
  return false unless Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline

  warn_about_incomplete_parallel_results(expected, seen)
  true
end

.previous_error?(error_exit_status) ⇒ Boolean

accepts it. test_unit sets status 0 on success, so SUCCESS must also be treated as “not a previous error”.

Returns:

  • (Boolean)


94
95
96
# File 'lib/simplecov/exit_handling.rb', line 94

def previous_error?(error_exit_status)
  !!(error_exit_status && error_exit_status != SimpleCov::ExitCodes::SUCCESS)
end

.process_result(result) ⇒ Object



135
136
137
138
139
# File 'lib/simplecov/exit_handling.rb', line 135

def process_result(result)
  result_exit_status = result_exit_status(result)
  write_last_run(result) if result_exit_status == SimpleCov::ExitCodes::SUCCESS
  result_exit_status
end

.process_results_and_report_errorObject



120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/simplecov/exit_handling.rb', line 120

def process_results_and_report_error
  exit_status = process_result(result)

  # Force exit with stored status (see github issue #5)
  return unless exit_status.positive?

  if print_errors
    warn SimpleCov::Color.colorize(
      "SimpleCov failed with exit #{exit_status} due to a coverage related error", :red
    )
  end
  Kernel.exit exit_status
end

.ready_to_process_results?Boolean

one that reports against thresholds, and only when its ‘wait_for_other_processes` confirmed every sibling reported. When the wait times out, the merged total is partial and comparing it against `minimum_coverage` / `maximum_coverage` would surface a spurious “below minimum” violation about the missing slice rather than a real shortfall.

Returns:

  • (Boolean)


116
117
118
# File 'lib/simplecov/exit_handling.rb', line 116

def ready_to_process_results?
  final_result_process? && result? && parallel_results_complete?
end

.resultObject

Returns the result for the current coverage run, merging it across test suites from cache using SimpleCov::ResultMerger if use_merging is activated (default)



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/simplecov/result_processing.rb', line 31

def result
  return @result if result?

  # Collect our coverage result
  process_coverage_result if defined?(Coverage) && Coverage.running?

  # If we're using merging of results, store the current result
  # first (if there is one), then merge the results and return those
  if merging
    wait_for_other_processes
    SimpleCov::ResultMerger.store_result(@result) if result?
    @result = SimpleCov::ResultMerger.merged_result
  end

  @result
end

.result?Boolean

Returns nil if the result has not been computed, otherwise the result.

Returns:

  • (Boolean)


49
50
51
# File 'lib/simplecov/result_processing.rb', line 49

def result?
  defined?(@result) && @result
end

.result_exit_status(result) ⇒ Object



141
142
143
# File 'lib/simplecov/exit_handling.rb', line 141

def result_exit_status(result)
  ExitCodes::ExitCodeHandling.call(result, coverage_limits: build_coverage_limits)
end

.round_coverage(coverage) ⇒ Object



100
101
102
# File 'lib/simplecov/result_processing.rb', line 100

def round_coverage(coverage)
  coverage.floor(2)
end

.run_exit_tasks!Object



70
71
72
73
74
75
76
77
# File 'lib/simplecov/exit_handling.rb', line 70

def run_exit_tasks!
  error_exit_status = exit_status_from_exception

  at_exit.call

  exit_and_report_previous_error(error_exit_status) if previous_error?(error_exit_status)
  process_results_and_report_error if ready_to_process_results?
end

.start(profile = nil) ⇒ Object

Sets up SimpleCov to run against your project. See README for the full DSL, or:

SimpleCov.start
SimpleCov.start 'rails'                # using a profile
SimpleCov.start { add_filter 'test' }  # with a config block


49
50
51
52
53
54
55
# File 'lib/simplecov.rb', line 49

def start(profile = nil, &)
  warn_about_start_in_dot_simplecov if @autoloading_dot_simplecov

  initial_setup(profile, &)
  start_tracking
  install_at_exit_hook
end

.start_trackingObject

Begin coverage tracking without applying configuration. Pairs with ‘SimpleCov.configure { … }` for callers that want to separate the two — for example a dogfood test that has already started `Coverage` itself before requiring simplecov, but still wants the process_start_time / pid / fork-hook bookkeeping.



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/simplecov.rb', line 110

def start_tracking
  require "coverage"
  warn_if_jruby_full_trace_disabled
  validate_coverage_criteria!
  # simplecov:disable — fork-hook is enabled via SimpleCov.enable_for_subprocesses, off by default
  require_relative "simplecov/process" if SimpleCov.enabled_for_subprocesses? &&
                                          ::Process.respond_to?(:_fork)
  # simplecov:enable

  # Trigger adapter selection now so the (possibly lazy) parallel_tests
  # gem load happens at start_tracking time rather than mid-suite.
  # `current` is memoized; subsequent calls are cheap.
  SimpleCov::ParallelAdapters.current

  @result = nil
  self.pid = Process.pid
  self.process_start_time = Time.now

  start_coverage_measurement
end

.wait_for_other_processesObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/simplecov/parallel_coordination.rb', line 31

def wait_for_other_processes
  adapter = SimpleCov::ParallelAdapters.current
  return unless adapter && final_result_process?

  # Native synchronization first (adapters that wrap a runner with a
  # real "wait" primitive — parallel_tests'
  # `wait_for_other_processes_to_finish` — implement this; adapters
  # without a native API no-op and rely on the polling fallback below).
  adapter.wait_for_siblings

  # The native wait can return before sibling at_exit handlers finish
  # writing resultsets, and adapters without a native wait have
  # nothing else. Either way, poll the resultset cache until all
  # expected workers have reported or a timeout is reached. Capture
  # the outcome so `ready_to_process_results?` can suppress min/max
  # threshold checks against a partial total.
  @parallel_results_complete = wait_for_parallel_results(adapter.expected_worker_count)
end

.wait_for_parallel_results(expected) ⇒ Object

before the deadline, false on timeout. Single-process runs (expected <= 1) short-circuit to true with no waiting.



60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/simplecov/parallel_coordination.rb', line 60

def wait_for_parallel_results(expected)
  return true unless expected > 1 # simplecov:disable branch — only false in real parallel runs

  deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + PARALLEL_RESULTS_WAIT_TIMEOUT
  loop do
    seen = SimpleCov::ResultMerger.read_resultset.size
    return true if seen >= expected
    return false if parallel_wait_timed_out?(deadline, expected, seen)

    sleep 0.1
  end
end

.warn_about_deferred_reportObject



57
58
59
60
61
62
63
64
65
66
67
# File 'lib/simplecov/exit_handling.rb', line 57

def warn_about_deferred_report
  return unless print_errors

  warn SimpleCov::Color.colorize(
    "Skipping SimpleCov report — this process tracked no application code and a newer " \
    "report already exists at #{coverage_path}. This usually means SimpleCov.start ran in a " \
    "parent process (e.g. a Rakefile or Rails' Bundler.require) that shelled out to the test " \
    "runner. See https://github.com/simplecov-ruby/simplecov/issues/581.",
    :yellow
  )
end

.warn_about_incomplete_parallel_results(expected, seen) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



83
84
85
86
87
88
89
90
91
92
# File 'lib/simplecov/parallel_coordination.rb', line 83

def warn_about_incomplete_parallel_results(expected, seen)
  return unless print_errors

  warn SimpleCov::Color.colorize(
    "Only #{seen} of #{expected} parallel-test workers reported within " \
    "#{PARALLEL_RESULTS_WAIT_TIMEOUT}s. Coverage totals are partial; " \
    "minimum / maximum coverage checks are skipped for this run.",
    :yellow
  )
end

.warn_about_start_in_dot_simplecovObject



72
73
74
75
76
77
78
79
80
81
82
# File 'lib/simplecov.rb', line 72

def warn_about_start_in_dot_simplecov
  return if @dot_simplecov_start_warned

  @dot_simplecov_start_warned = true
  warn "[DEPRECATION] Calling `SimpleCov.start` from `.simplecov` is deprecated and will " \
       "be removed in a future release. `.simplecov` should contain configuration only; " \
       "move the `SimpleCov.start` call into your `spec_helper.rb` / `test_helper.rb`. " \
       "Coverage tracking still begins for backward compatibility, but a future release " \
       "will require the explicit `SimpleCov.start` from a test helper. " \
       "See https://github.com/simplecov-ruby/simplecov/issues/581."
end

.with_dot_simplecov_autoloadObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Mark the duration of a ‘.simplecov` auto-load so any `SimpleCov.start` call inside the file can warn about the impending migration to a config-only file. Tracking still begins for backward compatibility; the warning is the cue to move `SimpleCov.start` into a test helper. See #581.



64
65
66
67
68
69
70
# File 'lib/simplecov.rb', line 64

def with_dot_simplecov_autoload
  previous = @autoloading_dot_simplecov
  @autoloading_dot_simplecov = true
  yield
ensure
  @autoloading_dot_simplecov = previous
end

.write_last_run(result) ⇒ Object

rounded down (see #679) so the next run can compute drift.



93
94
95
96
97
# File 'lib/simplecov/result_processing.rb', line 93

def write_last_run(result)
  SimpleCov::LastRun.write(
    result: result.coverage_statistics.transform_values { |stats| round_coverage(stats.percent) }
  )
end