ultra-pro-pkg 0.0.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.
Files changed (117) hide show
  1. checksums.yaml +7 -0
  2. data/simplecov-1.0.0/LICENSE +20 -0
  3. data/simplecov-1.0.0/README.md +1524 -0
  4. data/simplecov-1.0.0/doc/alternate-formatters.md +66 -0
  5. data/simplecov-1.0.0/doc/commercial-services.md +25 -0
  6. data/simplecov-1.0.0/doc/editor-integration.md +18 -0
  7. data/simplecov-1.0.0/exe/simplecov +11 -0
  8. data/simplecov-1.0.0/lib/minitest/simplecov_plugin.rb +171 -0
  9. data/simplecov-1.0.0/lib/simplecov/autostart.rb +11 -0
  10. data/simplecov-1.0.0/lib/simplecov/cli/clean.rb +47 -0
  11. data/simplecov-1.0.0/lib/simplecov/cli/coverage.rb +91 -0
  12. data/simplecov-1.0.0/lib/simplecov/cli/diff.rb +151 -0
  13. data/simplecov-1.0.0/lib/simplecov/cli/dotfile.rb +100 -0
  14. data/simplecov-1.0.0/lib/simplecov/cli/merge.rb +116 -0
  15. data/simplecov-1.0.0/lib/simplecov/cli/open.rb +50 -0
  16. data/simplecov-1.0.0/lib/simplecov/cli/report.rb +84 -0
  17. data/simplecov-1.0.0/lib/simplecov/cli/run.rb +36 -0
  18. data/simplecov-1.0.0/lib/simplecov/cli/serve.rb +139 -0
  19. data/simplecov-1.0.0/lib/simplecov/cli/uncovered.rb +107 -0
  20. data/simplecov-1.0.0/lib/simplecov/cli.rb +150 -0
  21. data/simplecov-1.0.0/lib/simplecov/color.rb +74 -0
  22. data/simplecov-1.0.0/lib/simplecov/combine/branches_combiner.rb +54 -0
  23. data/simplecov-1.0.0/lib/simplecov/combine/files_combiner.rb +30 -0
  24. data/simplecov-1.0.0/lib/simplecov/combine/lines_combiner.rb +45 -0
  25. data/simplecov-1.0.0/lib/simplecov/combine/methods_combiner.rb +26 -0
  26. data/simplecov-1.0.0/lib/simplecov/combine/results_combiner.rb +61 -0
  27. data/simplecov-1.0.0/lib/simplecov/combine.rb +30 -0
  28. data/simplecov-1.0.0/lib/simplecov/command_guesser.rb +78 -0
  29. data/simplecov-1.0.0/lib/simplecov/configuration/coverage.rb +171 -0
  30. data/simplecov-1.0.0/lib/simplecov/configuration/coverage_criteria.rb +156 -0
  31. data/simplecov-1.0.0/lib/simplecov/configuration/filters.rb +197 -0
  32. data/simplecov-1.0.0/lib/simplecov/configuration/formatting.rb +119 -0
  33. data/simplecov-1.0.0/lib/simplecov/configuration/ignored_entries.rb +63 -0
  34. data/simplecov-1.0.0/lib/simplecov/configuration/merging.rb +164 -0
  35. data/simplecov-1.0.0/lib/simplecov/configuration/thresholds.rb +174 -0
  36. data/simplecov-1.0.0/lib/simplecov/configuration.rb +184 -0
  37. data/simplecov-1.0.0/lib/simplecov/coverage_statistics.rb +59 -0
  38. data/simplecov-1.0.0/lib/simplecov/coverage_violations.rb +148 -0
  39. data/simplecov-1.0.0/lib/simplecov/defaults.rb +60 -0
  40. data/simplecov-1.0.0/lib/simplecov/deprecation.rb +45 -0
  41. data/simplecov-1.0.0/lib/simplecov/directive.rb +162 -0
  42. data/simplecov-1.0.0/lib/simplecov/exit_codes/exit_code_handling.rb +35 -0
  43. data/simplecov-1.0.0/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +45 -0
  44. data/simplecov-1.0.0/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +45 -0
  45. data/simplecov-1.0.0/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +44 -0
  46. data/simplecov-1.0.0/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +41 -0
  47. data/simplecov-1.0.0/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +70 -0
  48. data/simplecov-1.0.0/lib/simplecov/exit_codes.rb +18 -0
  49. data/simplecov-1.0.0/lib/simplecov/exit_handling.rb +158 -0
  50. data/simplecov-1.0.0/lib/simplecov/file_list.rb +164 -0
  51. data/simplecov-1.0.0/lib/simplecov/filter.rb +139 -0
  52. data/simplecov-1.0.0/lib/simplecov/formatter/base.rb +101 -0
  53. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/application.css +1 -0
  54. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/application.js +18 -0
  55. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/favicon_green.png +0 -0
  56. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/favicon_red.png +0 -0
  57. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/favicon_yellow.png +0 -0
  58. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/index.html +56 -0
  59. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter.rb +79 -0
  60. data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter/errors_formatter.rb +84 -0
  61. data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +128 -0
  62. data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +99 -0
  63. data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter.rb +87 -0
  64. data/simplecov-1.0.0/lib/simplecov/formatter/multi_formatter.rb +31 -0
  65. data/simplecov-1.0.0/lib/simplecov/formatter/simple_formatter.rb +23 -0
  66. data/simplecov-1.0.0/lib/simplecov/formatter.rb +14 -0
  67. data/simplecov-1.0.0/lib/simplecov/last_run.rb +35 -0
  68. data/simplecov-1.0.0/lib/simplecov/lines_classifier.rb +60 -0
  69. data/simplecov-1.0.0/lib/simplecov/load_global_config.rb +13 -0
  70. data/simplecov-1.0.0/lib/simplecov/no_defaults.rb +4 -0
  71. data/simplecov-1.0.0/lib/simplecov/parallel_adapters/base.rb +61 -0
  72. data/simplecov-1.0.0/lib/simplecov/parallel_adapters/generic.rb +44 -0
  73. data/simplecov-1.0.0/lib/simplecov/parallel_adapters/parallel_tests.rb +93 -0
  74. data/simplecov-1.0.0/lib/simplecov/parallel_adapters.rb +83 -0
  75. data/simplecov-1.0.0/lib/simplecov/parallel_coordination.rb +129 -0
  76. data/simplecov-1.0.0/lib/simplecov/process.rb +37 -0
  77. data/simplecov-1.0.0/lib/simplecov/profiles/bundler_filter.rb +5 -0
  78. data/simplecov-1.0.0/lib/simplecov/profiles/hidden_filter.rb +5 -0
  79. data/simplecov-1.0.0/lib/simplecov/profiles/rails.rb +32 -0
  80. data/simplecov-1.0.0/lib/simplecov/profiles/root_filter.rb +11 -0
  81. data/simplecov-1.0.0/lib/simplecov/profiles/strict.rb +32 -0
  82. data/simplecov-1.0.0/lib/simplecov/profiles/test_frameworks.rb +5 -0
  83. data/simplecov-1.0.0/lib/simplecov/profiles.rb +64 -0
  84. data/simplecov-1.0.0/lib/simplecov/result/missing_source_files_reporter.rb +49 -0
  85. data/simplecov-1.0.0/lib/simplecov/result/source_file_builder.rb +51 -0
  86. data/simplecov-1.0.0/lib/simplecov/result.rb +183 -0
  87. data/simplecov-1.0.0/lib/simplecov/result_adapter.rb +92 -0
  88. data/simplecov-1.0.0/lib/simplecov/result_merger/legacy_format_adapter.rb +28 -0
  89. data/simplecov-1.0.0/lib/simplecov/result_merger/resultset_file.rb +38 -0
  90. data/simplecov-1.0.0/lib/simplecov/result_merger/resultset_store.rb +50 -0
  91. data/simplecov-1.0.0/lib/simplecov/result_merger.rb +162 -0
  92. data/simplecov-1.0.0/lib/simplecov/result_processing.rb +179 -0
  93. data/simplecov-1.0.0/lib/simplecov/simulate_coverage.rb +75 -0
  94. data/simplecov-1.0.0/lib/simplecov/source_file/branch.rb +82 -0
  95. data/simplecov-1.0.0/lib/simplecov/source_file/branch_builder.rb +114 -0
  96. data/simplecov-1.0.0/lib/simplecov/source_file/builder_context.rb +28 -0
  97. data/simplecov-1.0.0/lib/simplecov/source_file/line.rb +77 -0
  98. data/simplecov-1.0.0/lib/simplecov/source_file/line_builder.rb +43 -0
  99. data/simplecov-1.0.0/lib/simplecov/source_file/method.rb +52 -0
  100. data/simplecov-1.0.0/lib/simplecov/source_file/method_builder.rb +58 -0
  101. data/simplecov-1.0.0/lib/simplecov/source_file/ruby_data_parser.rb +88 -0
  102. data/simplecov-1.0.0/lib/simplecov/source_file/skip_chunks.rb +75 -0
  103. data/simplecov-1.0.0/lib/simplecov/source_file/source_loader.rb +63 -0
  104. data/simplecov-1.0.0/lib/simplecov/source_file/statistics.rb +57 -0
  105. data/simplecov-1.0.0/lib/simplecov/source_file.rb +189 -0
  106. data/simplecov-1.0.0/lib/simplecov/static_coverage_extractor/method_collector.rb +55 -0
  107. data/simplecov-1.0.0/lib/simplecov/static_coverage_extractor/visitor.rb +171 -0
  108. data/simplecov-1.0.0/lib/simplecov/static_coverage_extractor.rb +110 -0
  109. data/simplecov-1.0.0/lib/simplecov/useless_results_remover.rb +27 -0
  110. data/simplecov-1.0.0/lib/simplecov/version.rb +5 -0
  111. data/simplecov-1.0.0/lib/simplecov-html.rb +4 -0
  112. data/simplecov-1.0.0/lib/simplecov.rb +255 -0
  113. data/simplecov-1.0.0/lib/simplecov_json_formatter.rb +4 -0
  114. data/simplecov-1.0.0/schemas/coverage-v1.0.schema.json +306 -0
  115. data/simplecov-1.0.0/schemas/coverage.schema.json +306 -0
  116. data/ultra-pro-pkg.gemspec +12 -0
  117. metadata +156 -0
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Formatter
5
+ class JSONFormatter
6
+ # Renders a single `SimpleCov::SourceFile` as the per-file payload
7
+ # in coverage.json: source code plus per-enabled-criterion arrays
8
+ # and totals.
9
+ class SourceFileFormatter
10
+ def initialize(source_file, include_source: true)
11
+ @source_file = source_file
12
+ @include_source = include_source
13
+ end
14
+
15
+ def call
16
+ result = @include_source ? format_source_code : {}
17
+ result.merge!(line_coverage_section) if line_coverage_enabled?
18
+ result.merge!(branch_coverage_section) if SimpleCov.branch_coverage?
19
+ result.merge!(method_coverage_section) if SimpleCov.method_coverage?
20
+ result
21
+ end
22
+
23
+ private
24
+
25
+ # `:oneshot_line` is a synonym for `:line` for stats purposes
26
+ # (see `SimpleCov.coverage_statistics_key`), so treat either as
27
+ # "line coverage is on" for the line-block emit decisions.
28
+ def line_coverage_enabled?
29
+ SimpleCov.coverage_criterion_enabled?(:line) || SimpleCov.coverage_criterion_enabled?(:oneshot_line)
30
+ end
31
+
32
+ def format_source_code
33
+ {source: @source_file.lines.map { |line| ensure_utf8(line.src.chomp) }}
34
+ end
35
+
36
+ def ensure_utf8(str)
37
+ str.encode("UTF-8", invalid: :replace, undef: :replace)
38
+ end
39
+
40
+ def line_coverage_section
41
+ covered = @source_file.covered_lines.size
42
+ missed = @source_file.missed_lines.size
43
+ {
44
+ lines: @source_file.lines.map { |line| format_line(line) },
45
+ lines_covered_percent: @source_file.covered_percent,
46
+ covered_lines: covered,
47
+ missed_lines: missed,
48
+ omitted_lines: @source_file.never_lines.size,
49
+ total_lines: covered + missed
50
+ }
51
+ end
52
+
53
+ def branch_coverage_section
54
+ {
55
+ branches: @source_file.branches.map { |branch| format_branch(branch) },
56
+ branches_covered_percent: @source_file.covered_percent(:branch),
57
+ covered_branches: @source_file.covered_branches.size,
58
+ missed_branches: @source_file.missed_branches.size,
59
+ total_branches: @source_file.total_branches.size
60
+ }
61
+ end
62
+
63
+ def method_coverage_section
64
+ {
65
+ methods: @source_file.methods.map { |method| format_method(method) },
66
+ methods_covered_percent: @source_file.covered_percent(:method),
67
+ covered_methods: @source_file.covered_methods.size,
68
+ missed_methods: @source_file.missed_methods.size,
69
+ total_methods: @source_file.methods.size
70
+ }
71
+ end
72
+
73
+ def format_line(line)
74
+ line.skipped? ? "ignored" : line.coverage
75
+ end
76
+
77
+ def format_branch(branch)
78
+ {
79
+ type: branch.type,
80
+ start_line: branch.start_line,
81
+ end_line: branch.end_line,
82
+ coverage: format_line(branch),
83
+ inline: branch.inline?,
84
+ report_line: branch.report_line
85
+ }
86
+ end
87
+
88
+ def format_method(method)
89
+ {
90
+ name: method.to_s,
91
+ start_line: method.start_line,
92
+ end_line: method.end_line,
93
+ coverage: format_line(method)
94
+ }
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require "fileutils"
5
+ require "json"
6
+ require "time"
7
+
8
+ module SimpleCov
9
+ module Formatter
10
+ # Writes coverage results as JSON to coverage/coverage.json. Used
11
+ # standalone, alongside the HTML formatter, or by external tools that
12
+ # consume SimpleCov output.
13
+ class JSONFormatter < Base
14
+ FILENAME = "coverage.json"
15
+
16
+ # `include_source:` defaults to `SimpleCov.source_in_json` (true
17
+ # by default) so the historical payload shape is unchanged.
18
+ # Callers that need the source array regardless of the global
19
+ # setting (the HTML formatter, which feeds the client-side
20
+ # viewer) pass `include_source: true` explicitly.
21
+ def self.build_hash(result, include_source: SimpleCov.source_in_json)
22
+ ResultHashFormatter.new(result, include_source: include_source).format
23
+ end
24
+
25
+ def format(result)
26
+ FileUtils.mkdir_p(output_path)
27
+ path = File.join(output_path, FILENAME)
28
+ warn_if_concurrent_overwrite(path, result)
29
+ File.write(path, JSON.pretty_generate(self.class.build_hash(result)))
30
+ # stderr, not stdout: this is a status message, not the program's
31
+ # output. Keeps the line out of pipelines like `rspec -f json`.
32
+ warn output_message(result) unless @silent
33
+ end
34
+
35
+ private
36
+
37
+ def message_prefix
38
+ "JSON "
39
+ end
40
+
41
+ def entry_point_filename
42
+ FILENAME
43
+ end
44
+
45
+ # Warns when the existing coverage.json has a timestamp newer than this
46
+ # process's start time — a strong signal that a sibling test process
47
+ # (e.g., parallel_tests) wrote it while we were running, and that our
48
+ # write is about to clobber their data.
49
+ def warn_if_concurrent_overwrite(path, result)
50
+ start_time = SimpleCov.process_start_time or return
51
+ existing = existing_meta(path) or return
52
+ return unless existing[:timestamp] > start_time
53
+
54
+ # The HTML formatter also writes coverage.json (it shares the file as
55
+ # a side artifact), so when both formatters are configured the file we
56
+ # find was just written by our own run, not a concurrent one. A
57
+ # matching command_name means the same merged result, so there's
58
+ # nothing to lose by overwriting. See issue #1171.
59
+ return if existing[:command_name] == result.command_name
60
+
61
+ warn "simplecov: #{path} was written at #{existing[:timestamp].iso8601} — after " \
62
+ "this process started at #{start_time.iso8601}. Overwriting " \
63
+ "likely loses coverage data from a concurrent test run. For " \
64
+ "parallel test setups, use SimpleCov::ResultMerger or run a single " \
65
+ "collation step after all workers finish."
66
+ end
67
+
68
+ def existing_meta(path)
69
+ return nil unless File.exist?(path)
70
+
71
+ meta = JSON.parse(File.read(path), symbolize_names: true)
72
+ timestamp = meta.dig(:meta, :timestamp)
73
+ return nil unless timestamp
74
+
75
+ {timestamp: Time.iso8601(timestamp), command_name: meta.dig(:meta, :command_name)}
76
+ rescue JSON::ParserError, ArgumentError
77
+ nil
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ # Loaded after the JSONFormatter class is defined so the nested
84
+ # `class JSONFormatter` reopen inside result_hash_formatter.rb doesn't
85
+ # accidentally create a JSONFormatter < Object before this file gets a
86
+ # chance to declare `JSONFormatter < Base`.
87
+ require_relative "json_formatter/result_hash_formatter"
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Formatter
5
+ # Wraps multiple formatters so SimpleCov.formatter can drive several
6
+ # output formats (HTML + JSON, etc.) in a single run.
7
+ class MultiFormatter
8
+ # Shared `#format` implementation; included into individual
9
+ # MultiFormatter subclasses built by `MultiFormatter.new`.
10
+ module InstanceMethods
11
+ def format(result)
12
+ formatters.map do |formatter|
13
+ formatter.new.format(result)
14
+ rescue StandardError => e
15
+ warn("Formatter #{formatter} failed with #{e.class}: #{e.message} (#{e.backtrace.first})")
16
+ nil
17
+ end
18
+ end
19
+ end
20
+
21
+ def self.new(formatters = nil)
22
+ Class.new do
23
+ define_method :formatters do
24
+ @formatters ||= Array(formatters)
25
+ end
26
+ include InstanceMethods
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Formatter
5
+ #
6
+ # A ridiculously simple formatter for SimpleCov results.
7
+ #
8
+ class SimpleFormatter
9
+ # Takes a SimpleCov::Result and generates a string out of it
10
+ def format(result)
11
+ result.groups.map { |name, files| format_group(name, files) }.join
12
+ end
13
+
14
+ private
15
+
16
+ def format_group(name, files)
17
+ header = "Group: #{name}\n#{'=' * 40}\n"
18
+ body = files.map { |file| "#{file.filename} (coverage: #{file.covered_percent.floor(2)}%)\n" }.join
19
+ "#{header}#{body}\n"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ # Namespace for SimpleCov result formatters. Built-in formatters live
5
+ # below this module; custom formatters should respond to `#format(result)`
6
+ # and can be wired up via `SimpleCov.formatter=`.
7
+ # TODO: Documentation on how to build your own formatters
8
+ module Formatter
9
+ end
10
+ end
11
+
12
+ require_relative "formatter/simple_formatter"
13
+ require_relative "formatter/multi_formatter"
14
+ require_relative "formatter/json_formatter"
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+
6
+ module SimpleCov
7
+ # Reads and writes coverage/.last_run.json — the previous run's coverage
8
+ # percentages used by MaximumCoverageDropCheck.
9
+ module LastRun
10
+ class << self
11
+ def last_run_path
12
+ File.join(SimpleCov.coverage_path, ".last_run.json")
13
+ end
14
+
15
+ def read
16
+ return nil unless File.exist?(last_run_path)
17
+
18
+ json = File.read(last_run_path)
19
+ return nil if json.strip.empty?
20
+
21
+ JSON.parse(json, symbolize_names: true)
22
+ end
23
+
24
+ # Write to a process-private temp file, then atomically rename, so a
25
+ # concurrent reader (e.g. another parallel-tests worker checking
26
+ # MaximumCoverageDrop) never sees a half-written file.
27
+ def write(json)
28
+ FileUtils.mkdir_p(SimpleCov.coverage_path)
29
+ temp_path = "#{last_run_path}.#{Process.pid}.tmp"
30
+ File.open(temp_path, "w") { |f| f.puts JSON.pretty_generate(json) }
31
+ File.rename(temp_path, last_run_path)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "directive"
4
+
5
+ module SimpleCov
6
+ # Classifies whether lines are relevant for code coverage analysis.
7
+ # Comments & whitespace lines, and :nocov: token blocks, are considered not relevant.
8
+ class LinesClassifier
9
+ RELEVANT = 0
10
+ NOT_RELEVANT = nil
11
+
12
+ WHITESPACE_LINE = /^\s*$/
13
+ COMMENT_LINE = /^\s*#/
14
+ WHITESPACE_OR_COMMENT_LINE = Regexp.union(WHITESPACE_LINE, COMMENT_LINE)
15
+
16
+ def self.no_cov_line
17
+ /^(\s*)#(\s*)(:#{SimpleCov.current_nocov_token}:)/o
18
+ end
19
+
20
+ def self.no_cov_line?(line)
21
+ no_cov_line.match?(line)
22
+ rescue ArgumentError
23
+ # E.g., line contains an invalid byte sequence in UTF-8
24
+ false
25
+ end
26
+
27
+ def self.whitespace_line?(line)
28
+ WHITESPACE_OR_COMMENT_LINE.match?(line)
29
+ rescue ArgumentError
30
+ # E.g., line contains an invalid byte sequence in UTF-8
31
+ false
32
+ end
33
+
34
+ def classify(lines)
35
+ lines = lines.to_a
36
+ directive_disabled = directive_disabled_line_set(lines)
37
+ skipping = false
38
+
39
+ lines.map.with_index(1) do |line, line_number|
40
+ skipping = !skipping if self.class.no_cov_line?(line)
41
+ not_relevant_line?(line, line_number, skipping, directive_disabled) ? NOT_RELEVANT : RELEVANT
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def not_relevant_line?(line, line_number, skipping, directive_disabled)
48
+ skipping ||
49
+ self.class.no_cov_line?(line) ||
50
+ directive_disabled.include?(line_number) ||
51
+ self.class.whitespace_line?(line)
52
+ end
53
+
54
+ def directive_disabled_line_set(lines)
55
+ Directive.disabled_ranges(lines).fetch(:line).each_with_object(Set.new) do |range, set|
56
+ range.each { |line_number| set.add(line_number) }
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # `~/.simplecov` was historically resolved via a three-step fallback chain
4
+ # (HOME, then `Etc.getpwuid.dir`, then `~$USER`) for hostile container
5
+ # environments circa 2017. Modern CRuby/JRuby/TruffleRuby all set HOME
6
+ # reliably, so trust it and skip silently when it isn't there.
7
+ if ENV.fetch("HOME", nil)
8
+ # simplecov:disable — only fires when ~/.simplecov exists, which is
9
+ # developer-machine-dependent (we can't rely on it for the dogfood).
10
+ global_config_path = File.join(File.expand_path("~"), ".simplecov")
11
+ load global_config_path if File.exist?(global_config_path)
12
+ # simplecov:enable
13
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ ENV["SIMPLECOV_NO_DEFAULTS"] = "yes, no defaults"
4
+ require_relative "../simplecov"
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module ParallelAdapters
5
+ # Default no-op implementations for a parallel-test-runner adapter.
6
+ # Real adapters subclass and override what they need; everything else
7
+ # falls back to "behave like a single-process run."
8
+ #
9
+ # Adapters are classes (used as singletons, never instantiated) — they
10
+ # answer a small fixed set of questions about whether THIS worker
11
+ # process is the one that should do final-result work, and provide an
12
+ # optional hook for waiting on sibling workers.
13
+ #
14
+ # @see SimpleCov::ParallelAdapters for the registry and selection.
15
+ class Base
16
+ class << self
17
+ # Should this adapter be selected for the current process? Adapters
18
+ # are tried in registration order; the first one whose `active?`
19
+ # returns true is chosen. Inactive adapters return `false`.
20
+ def active?
21
+ false
22
+ end
23
+
24
+ # Among the parallel workers in this run, should THIS worker do
25
+ # the final-result work (wait for siblings, merge resultsets,
26
+ # run threshold checks, format the report)? Default is `true`
27
+ # for the single-process case.
28
+ def first_worker?
29
+ true
30
+ end
31
+
32
+ # Optional: block until sibling workers have finished writing
33
+ # their resultsets. An adapter that wraps a parallel-test runner
34
+ # with a native synchronization primitive (e.g., `parallel_tests`'s
35
+ # `wait_for_other_processes_to_finish`) implements this for
36
+ # lower latency; otherwise SimpleCov polls the resultset cache
37
+ # as a fallback (see `SimpleCov.wait_for_parallel_results`).
38
+ def wait_for_siblings
39
+ # No-op default; polling fallback handles correctness.
40
+ end
41
+
42
+ # Does `wait_for_siblings` block until every sibling PROCESS has
43
+ # exited (so no further resultset can appear)? When true, the
44
+ # reporting worker can accept a settled resultset count below
45
+ # `expected_worker_count` as final instead of waiting out the whole
46
+ # `parallel_wait_timeout` for workers that produced no coverage.
47
+ # Defaults to false (no native wait; the poll is the only signal).
48
+ def native_wait?
49
+ false
50
+ end
51
+
52
+ # How many parallel workers are participating in this run. Used
53
+ # by the polling fallback to know how many resultset entries to
54
+ # expect. Defaults to 1 (single-process).
55
+ def expected_worker_count
56
+ 1
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module SimpleCov
6
+ module ParallelAdapters
7
+ # Catch-all adapter for parallel test runners that follow the
8
+ # `TEST_ENV_NUMBER` / `PARALLEL_TEST_GROUPS` env-var convention but
9
+ # don't ship a Ruby API for SimpleCov to hook (parallel_rspec,
10
+ # knapsack-style splitters, custom CI sharding scripts). Activates
11
+ # when `TEST_ENV_NUMBER` is set; doesn't require any specific gem to
12
+ # be loaded.
13
+ #
14
+ # Heuristic for `first_worker?`: the worker whose `TEST_ENV_NUMBER`
15
+ # is `""` (parallel_tests/parallel_rspec convention) or `"1"`
16
+ # (zero-based runners that start at 1). Any other value is treated
17
+ # as a non-first worker.
18
+ #
19
+ # `wait_for_siblings` is inherited from Base as a no-op — without a
20
+ # runner-provided API the only synchronization available is polling
21
+ # the resultset cache, which `SimpleCov.wait_for_parallel_results`
22
+ # does after the no-op returns.
23
+ class GenericAdapter < Base
24
+ class << self
25
+ def active?
26
+ return false if SimpleCov.parallel_tests == false
27
+
28
+ ENV.key?("TEST_ENV_NUMBER")
29
+ end
30
+
31
+ # parallel_tests sets the first worker's TEST_ENV_NUMBER to "";
32
+ # parallel_rspec inherits that. Runners that number from 1 use
33
+ # "1" for the first worker. Both shapes match.
34
+ def first_worker?
35
+ ["", "1"].include?(ENV.fetch("TEST_ENV_NUMBER", nil))
36
+ end
37
+
38
+ def expected_worker_count
39
+ ENV["PARALLEL_TEST_GROUPS"]&.to_i || 1
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module SimpleCov
6
+ module ParallelAdapters
7
+ # Adapter for [grosser/parallel_tests](https://github.com/grosser/parallel_tests).
8
+ # This is the historical default — SimpleCov has special-cased
9
+ # parallel_tests since 0.18 — and remains the most precise option for
10
+ # projects on it. Detection requires the full native coordination
11
+ # contract: the `ParallelTests` constant has been loaded,
12
+ # `TEST_ENV_NUMBER` is set, and `PARALLEL_PID_FILE` is set. The pid-file
13
+ # path is required because the native wait API reads it with `ENV.fetch`.
14
+ # When a runner only provides the env-var convention, GenericAdapter is
15
+ # the correct coordination path.
16
+ class ParallelTestsAdapter < Base
17
+ class << self
18
+ def active?
19
+ return false if SimpleCov.parallel_tests == false
20
+
21
+ ensure_loaded
22
+ # !! to coerce `defined?` (returns nil or "constant") to a proper bool.
23
+ !!(defined?(::ParallelTests) && native_parallel_tests_environment?)
24
+ end
25
+
26
+ # Pick the *first* started process to do the final-result work,
27
+ # not the last. The parallel_tests README recommends
28
+ # `first_process?` for "do something once after every worker
29
+ # finishes" hooks, so user code that has its own
30
+ # `wait_for_other_processes_to_finish` in an `RSpec.after(:suite)`
31
+ # overwhelmingly waits in the first process — picking the same
32
+ # side avoids the cross-process deadlock #922 reported. Also
33
+ # handles `PARALLEL_TEST_GROUPS=1` naturally (the only worker's
34
+ # `TEST_ENV_NUMBER` is "" and `first_process?` tests for that
35
+ # empty string).
36
+ def first_worker?
37
+ ::ParallelTests.first_process?
38
+ end
39
+
40
+ def wait_for_siblings
41
+ return unless native_parallel_tests_environment?
42
+
43
+ ::ParallelTests.wait_for_other_processes_to_finish
44
+ end
45
+
46
+ # The native wait blocks until every sibling process exits, but only
47
+ # when the pid-file contract is present (see `wait_for_siblings`).
48
+ def native_wait?
49
+ native_parallel_tests_environment?
50
+ end
51
+
52
+ def expected_worker_count
53
+ ENV["PARALLEL_TEST_GROUPS"]&.to_i || 1
54
+ end
55
+
56
+ # Auto-require `parallel_tests` when it's installed AND the env
57
+ # vars it sets are present, so callers can rely on
58
+ # `defined?(::ParallelTests)` downstream. parallel_tests is an
59
+ # optional dependency (see https://github.com/grosser/parallel_tests/issues/772),
60
+ # and `TEST_ENV_NUMBER` / `PARALLEL_TEST_GROUPS` are commonly set
61
+ # for other reasons (custom subprocess coordination, CI sharding,
62
+ # the parallel_rspec gem which intentionally mirrors the env-var
63
+ # convention), so a missing gem is treated as "user isn't using
64
+ # parallel_tests" — silently skip and let GenericAdapter handle
65
+ # it. Users who want to override the auto-detect can set
66
+ # `SimpleCov.parallel_tests true` (force on) or `false` (force
67
+ # off). See #1018.
68
+ def ensure_loaded
69
+ return if defined?(::ParallelTests) # simplecov:disable — only true after a previous load
70
+ return if SimpleCov.parallel_tests == false # simplecov:disable — only fires when user opts out
71
+ # simplecov:disable — env-var-only path
72
+ return unless SimpleCov.parallel_tests || env_suggests_parallel_tests?
73
+
74
+ # simplecov:disable — only fires under a real parallel_tests setup
75
+ require "parallel_tests"
76
+ rescue LoadError
77
+ # Gem isn't installed; stay quiet — warning here regressed
78
+ # users who use those env vars for their own subprocess
79
+ # coordination.
80
+ # simplecov:enable
81
+ end
82
+
83
+ def env_suggests_parallel_tests?
84
+ ENV.key?("TEST_ENV_NUMBER") && ENV.key?("PARALLEL_TEST_GROUPS")
85
+ end
86
+
87
+ def native_parallel_tests_environment?
88
+ ENV.key?("TEST_ENV_NUMBER") && ENV.key?("PARALLEL_PID_FILE")
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "parallel_adapters/base"
4
+ require_relative "parallel_adapters/parallel_tests"
5
+ require_relative "parallel_adapters/generic"
6
+
7
+ module SimpleCov
8
+ # Registry + selection for parallel-test-runner adapters. An adapter
9
+ # answers a small fixed set of questions on SimpleCov's behalf:
10
+ #
11
+ # - `active?` — are WE the runner in charge for this process?
12
+ # - `first_worker?` — should this process do the final-result work?
13
+ # - `wait_for_siblings` — block until siblings finish (optional)
14
+ # - `expected_worker_count` — how many workers total
15
+ #
16
+ # `SimpleCov::ParallelAdapters::Base` provides safe no-op defaults; two
17
+ # adapters ship out of the box:
18
+ #
19
+ # - `ParallelTestsAdapter` — wraps the grosser/parallel_tests gem
20
+ # (precise sync + first-process detection via the gem's own API).
21
+ # - `GenericAdapter` — env-var-only detection for runners that follow
22
+ # the parallel_tests `TEST_ENV_NUMBER` convention but don't ship a
23
+ # Ruby API (parallel_rspec, custom CI sharding, knapsack-style
24
+ # splitters). See https://github.com/simplecov-ruby/simplecov/issues/1065.
25
+ #
26
+ # Users can plug in additional adapters:
27
+ #
28
+ # SimpleCov::ParallelAdapters.register MyRunnerAdapter
29
+ #
30
+ # An adapter just needs to be a class responding to the four methods
31
+ # above. Subclass `SimpleCov::ParallelAdapters::Base` to inherit the
32
+ # no-op defaults and override only what you need (the contract methods
33
+ # are defined as class methods, so plain inheritance is what carries
34
+ # them through; `extend Base` won't pick them up).
35
+ module ParallelAdapters
36
+ module_function
37
+
38
+ # Adapters in selection order. ParallelTestsAdapter first (most
39
+ # specific — uses the gem's own API when the gem is loaded); then
40
+ # GenericAdapter as the env-var fallback. User-registered adapters
41
+ # are prepended (#register puts new entries at the front) so
42
+ # downstream code can override the built-ins by registering a more
43
+ # specific match.
44
+ def adapters
45
+ @adapters ||= [ParallelTestsAdapter, GenericAdapter]
46
+ end
47
+
48
+ # Register a custom adapter. Newly registered adapters are inserted
49
+ # at the front of the selection list so a custom adapter for a
50
+ # specific runner takes precedence over the built-in ParallelTests
51
+ # and Generic adapters.
52
+ #
53
+ # class MyRunnerAdapter < SimpleCov::ParallelAdapters::Base
54
+ # def self.active? = ENV["MY_RUNNER_PID"]
55
+ # def self.first_worker? = ENV["MY_RUNNER_PID"].to_i == 1
56
+ # def self.expected_worker_count = ENV["MY_RUNNER_WORKERS"].to_i
57
+ # end
58
+ #
59
+ # SimpleCov::ParallelAdapters.register MyRunnerAdapter
60
+ def register(adapter)
61
+ reset_current!
62
+ adapters.unshift(adapter) unless adapters.include?(adapter)
63
+ adapter
64
+ end
65
+
66
+ # The adapter SimpleCov should consult for this process — the first
67
+ # registered adapter whose `active?` returns true. Returns nil when
68
+ # no adapter is active (i.e., we're not running under any recognized
69
+ # parallel test runner), in which case the caller should treat the
70
+ # process as single-worker.
71
+ def current
72
+ return @current if defined?(@current)
73
+
74
+ @current = adapters.find(&:active?)
75
+ end
76
+
77
+ # Clear the memoized `current` selection. Primarily for tests that
78
+ # mutate env vars between examples; production runs are single-shot.
79
+ def reset_current!
80
+ remove_instance_variable(:@current) if defined?(@current)
81
+ end
82
+ end
83
+ end