lib/simplecov.rb
# frozen_string_literal: true # # Code coverage for ruby. Please check out README for a full introduction. # module SimpleCov # Raised when a user's configuration is internally inconsistent — e.g. # every coverage criterion has been disabled. class ConfigurationError < StandardError; end # Maps SimpleCov's criterion names to the keys `Coverage.start` expects. # Lives at module scope (not inside `class << self`) so it can be # declared in the RBS signatures; lexical scoping keeps every existing # reference inside the singleton class working. CRITERION_TO_RUBY_COVERAGE = { branch: :branches, line: :lines, method: :methods, oneshot_line: :oneshot_lines }.freeze class << self attr_accessor :pid # 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. attr_accessor :process_start_time # A monotonically increasing serial the parent assigns to each forked # subprocess (see SimpleCov::ProcessForkHook). The default `at_fork` # builds the worker's command_name from this rather than the OS pid: # the serial sequence is the same from one run to the next, so a re-run # overwrites the previous run's resultset entries instead of writing # uniquely-named ones that pile up until merge_timeout. See issue #1171. def subprocess_serial @subprocess_serial ||= 0 end # @api private — bump the serial in the parent before a fork so the # child inherits its own ordinal via copy-on-write. def next_subprocess_serial! @subprocess_serial = subprocess_serial + 1 end # @api private — true in a process that was forked while coverage was # running (set by SimpleCov::ProcessForkHook in the child). Such a child # stores its own slice but must not act as the final-result process: the # process that forked it merges every slice and produces the report. Only # consulted when no parallel-test adapter is active, since adapters answer # `first_worker?` themselves. See issue #1171. def forked_subprocess? !!(defined?(@forked_subprocess) && @forked_subprocess) end # @api private — marked in the child immediately after a fork. def mark_forked_subprocess! @forked_subprocess = true end # Should we take care of at_exit behavior or something else? Used by the # minitest plugin. See lib/minitest/simplecov_plugin.rb. attr_accessor :external_at_exit # `:oneshot_line` data is folded into the `:line` bucket of # `coverage_statistics` by `ResultAdapter`, so use `:line` to look # up stats for either criterion. def coverage_statistics_key(criterion) criterion == :oneshot_line ? :line : criterion end # Coerce to a proper boolean so rspec-mocks 4's predicate matcher # (`expect(...).not_to be_external_at_exit`) accepts the result. def external_at_exit? !!@external_at_exit end # # 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 # def start(profile = nil, &) warn_about_start_in_dot_simplecov if @autoloading_dot_simplecov initial_setup(profile, &) start_tracking install_at_exit_hook end # @api private # # 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. def with_dot_simplecov_autoload # Read in the ensure clause, where flow analysis cannot see the # assignment above; anchor the type here. previous = @autoloading_dot_simplecov # : bool? @autoloading_dot_simplecov = true yield ensure # @type var previous: bool? @autoloading_dot_simplecov = previous end 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 # # 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`. # def install_at_exit_hook return if @at_exit_hook_installed @at_exit_hook_installed = true # Never defer in a forked child: Minitest pins its after_run at_exit # to the pid that armed autorun, so the deferral target can't fire # there and the child's resultset would be silently dropped. See # issue #1227. defer_to_minitest_after_run if minitest_autorun_pending? && !forked_subprocess? Kernel.at_exit do next if SimpleCov.external_at_exit? SimpleCov.at_exit_behavior end end # # 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. # 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 private # # Trigger Coverage.start with the configured criteria. Every supported # runtime (CRuby >= 3.2, JRuby >= 10, TruffleRuby >= 22) accepts the # criteria-hash form, so no compatibility fallback is needed. # def start_coverage_measurement start_arguments = coverage_criteria.to_h do |criterion| [CRITERION_TO_RUBY_COVERAGE.fetch(criterion), true] end start_arguments[:eval] = true if coverage_for_eval_enabled? Coverage.start(**start_arguments) unless Coverage.running? end # `Rake::TestTask` runs `ruby -e 'require "minitest/autorun"; ...'`, # which means Minitest's at_exit registers before SimpleCov's. Since # at_exit fires LIFO, SimpleCov's hook would otherwise run *before* # Minitest gets a chance to invoke the tests — and format an empty # resultset. When we can see that Minitest is loaded and its autorun # is armed, route the report through `Minitest.after_run` instead, # which fires after the suite completes. See issues #1099 and #1112. def minitest_autorun_pending? return false unless defined?(Minitest) && Minitest.respond_to?(:after_run) return false unless Minitest.class_variable_defined?(:@@installed_at_exit) Minitest.class_variable_get(:@@installed_at_exit) end def defer_to_minitest_after_run self.external_at_exit = true Minitest.after_run { SimpleCov.at_exit_behavior } end # JRuby coverage data is unreliable unless full-trace mode is enabled. # @see https://github.com/jruby/jruby/issues/1196 # @see https://github.com/simplecov-ruby/simplecov/issues/420 # @see https://github.com/simplecov-ruby/simplecov/issues/86 def warn_if_jruby_full_trace_disabled return unless defined?(JRUBY_VERSION) && defined?(JRuby) # simplecov:disable — JRuby-only branch # simplecov:disable — JRuby-only branches; unreachable from CRuby # `org` is JRuby's Java-package entry point; it does not exist on # CRuby, so no RBS declaration can be truthful here. return if org.jruby.RubyInstanceConfig.FULL_TRACE_ENABLED # steep:ignore NoMethod warn 'Coverage may be inaccurate; set the "--debug" command line option, ' \ 'or do JRUBY_OPTS="--debug" ' \ 'or set the "debug.fullTrace=true" option in your .jrubyrc' # simplecov:enable end end end # requires are down here for a load order reason I'm not sure what it is about require "forwardable" require_relative "simplecov/color" require_relative "simplecov/deprecation" require_relative "simplecov/configuration" SimpleCov.extend SimpleCov::Configuration require_relative "simplecov/coverage_statistics" require_relative "simplecov/coverage_violations" require_relative "simplecov/exit_codes" require_relative "simplecov/profiles" require_relative "simplecov/source_file/line" require_relative "simplecov/source_file/branch" require_relative "simplecov/source_file/method" require_relative "simplecov/source_file" require_relative "simplecov/file_list" require_relative "simplecov/result" require_relative "simplecov/filter" require_relative "simplecov/formatter" require_relative "simplecov/last_run" require_relative "simplecov/lines_classifier" require_relative "simplecov/result_merger" require_relative "simplecov/parallel_adapters" require_relative "simplecov/command_guesser" require_relative "simplecov/version" require_relative "simplecov/result_adapter" require_relative "simplecov/combine" require_relative "simplecov/combine/branches_combiner" require_relative "simplecov/combine/methods_combiner" require_relative "simplecov/combine/files_combiner" require_relative "simplecov/combine/lines_combiner" require_relative "simplecov/combine/results_combiner" require_relative "simplecov/useless_results_remover" require_relative "simplecov/simulate_coverage" require_relative "simplecov/result_processing" require_relative "simplecov/exit_handling" require_relative "simplecov/parallel_coordination" # Load default config # simplecov:disable — env-var only set by aruba feature tests require_relative "simplecov/defaults" unless ENV["SIMPLECOV_NO_DEFAULTS"]