simplecov 0.6.0 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +163 -80
  3. data/LICENSE +1 -1
  4. data/README.md +776 -277
  5. data/doc/alternate-formatters.md +71 -0
  6. data/doc/commercial-services.md +25 -0
  7. data/doc/editor-integration.md +18 -0
  8. data/lib/minitest/simplecov_plugin.rb +15 -0
  9. data/lib/simplecov/combine/branches_combiner.rb +32 -0
  10. data/lib/simplecov/combine/files_combiner.rb +24 -0
  11. data/lib/simplecov/combine/lines_combiner.rb +43 -0
  12. data/lib/simplecov/combine/results_combiner.rb +60 -0
  13. data/lib/simplecov/combine.rb +30 -0
  14. data/lib/simplecov/command_guesser.rb +53 -38
  15. data/lib/simplecov/configuration.rb +478 -193
  16. data/lib/simplecov/coverage_statistics.rb +56 -0
  17. data/lib/simplecov/default_formatter.rb +20 -0
  18. data/lib/simplecov/defaults.rb +40 -44
  19. data/lib/simplecov/exit_codes/exit_code_handling.rb +29 -0
  20. data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +83 -0
  21. data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +54 -0
  22. data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +53 -0
  23. data/lib/simplecov/exit_codes.rb +15 -0
  24. data/lib/simplecov/file_list.rb +112 -36
  25. data/lib/simplecov/filter.rb +54 -4
  26. data/lib/simplecov/formatter/multi_formatter.rb +32 -0
  27. data/lib/simplecov/formatter/simple_formatter.rb +21 -15
  28. data/lib/simplecov/formatter.rb +4 -1
  29. data/lib/simplecov/last_run.rb +28 -0
  30. data/lib/simplecov/lines_classifier.rb +48 -0
  31. data/lib/simplecov/load_global_config.rb +8 -0
  32. data/lib/simplecov/no_defaults.rb +4 -0
  33. data/lib/simplecov/process.rb +19 -0
  34. data/lib/simplecov/profiles/bundler_filter.rb +5 -0
  35. data/lib/simplecov/profiles/hidden_filter.rb +5 -0
  36. data/lib/simplecov/profiles/rails.rb +18 -0
  37. data/lib/simplecov/profiles/root_filter.rb +10 -0
  38. data/lib/simplecov/profiles/test_frameworks.rb +8 -0
  39. data/lib/simplecov/profiles.rb +35 -0
  40. data/lib/simplecov/result.rb +33 -64
  41. data/lib/simplecov/result_adapter.rb +30 -0
  42. data/lib/simplecov/result_merger.rb +178 -64
  43. data/lib/simplecov/simulate_coverage.rb +29 -0
  44. data/lib/simplecov/source_file/branch.rb +84 -0
  45. data/lib/simplecov/source_file/line.rb +72 -0
  46. data/lib/simplecov/source_file.rb +304 -123
  47. data/lib/simplecov/useless_results_remover.rb +18 -0
  48. data/lib/simplecov/version.rb +4 -2
  49. data/lib/simplecov.rb +396 -49
  50. metadata +81 -242
  51. data/.gitignore +0 -30
  52. data/.rvmrc +0 -1
  53. data/.travis.yml +0 -13
  54. data/Gemfile +0 -9
  55. data/Rakefile +0 -16
  56. data/cucumber.yml +0 -13
  57. data/features/config_adapters.feature +0 -44
  58. data/features/config_autoload.feature +0 -46
  59. data/features/config_command_name.feature +0 -33
  60. data/features/config_coverage_dir.feature +0 -20
  61. data/features/config_deactivate_merging.feature +0 -42
  62. data/features/config_merge_timeout.feature +0 -38
  63. data/features/config_nocov_token.feature +0 -79
  64. data/features/config_project_name.feature +0 -27
  65. data/features/config_styles.feature +0 -93
  66. data/features/cucumber_basic.feature +0 -29
  67. data/features/merging_test_unit_and_rspec.feature +0 -44
  68. data/features/rspec_basic.feature +0 -31
  69. data/features/rspec_fails_on_initialization.feature +0 -14
  70. data/features/rspec_groups_and_filters_basic.feature +0 -29
  71. data/features/rspec_groups_and_filters_complex.feature +0 -35
  72. data/features/rspec_groups_using_filter_class.feature +0 -40
  73. data/features/rspec_without_simplecov.feature +0 -20
  74. data/features/skipping_code_blocks_manually.feature +0 -70
  75. data/features/step_definitions/html_steps.rb +0 -45
  76. data/features/step_definitions/simplecov_steps.rb +0 -61
  77. data/features/step_definitions/transformers.rb +0 -13
  78. data/features/step_definitions/web_steps.rb +0 -64
  79. data/features/support/env.rb +0 -26
  80. data/features/test_unit_basic.feature +0 -34
  81. data/features/test_unit_groups_and_filters_basic.feature +0 -29
  82. data/features/test_unit_groups_and_filters_complex.feature +0 -35
  83. data/features/test_unit_groups_using_filter_class.feature +0 -40
  84. data/features/test_unit_without_simplecov.feature +0 -20
  85. data/features/unicode_compatiblity.feature +0 -67
  86. data/lib/simplecov/adapters.rb +0 -29
  87. data/lib/simplecov/merge_helpers.rb +0 -39
  88. data/lib/simplecov/railtie.rb +0 -7
  89. data/lib/simplecov/railties/tasks.rake +0 -11
  90. data/simplecov.gemspec +0 -28
  91. data/test/faked_project/Gemfile +0 -6
  92. data/test/faked_project/Rakefile +0 -8
  93. data/test/faked_project/cucumber.yml +0 -13
  94. data/test/faked_project/features/step_definitions/my_steps.rb +0 -23
  95. data/test/faked_project/features/support/env.rb +0 -12
  96. data/test/faked_project/features/test_stuff.feature +0 -6
  97. data/test/faked_project/lib/faked_project/framework_specific.rb +0 -18
  98. data/test/faked_project/lib/faked_project/meta_magic.rb +0 -24
  99. data/test/faked_project/lib/faked_project/some_class.rb +0 -29
  100. data/test/faked_project/lib/faked_project.rb +0 -11
  101. data/test/faked_project/spec/faked_spec.rb +0 -11
  102. data/test/faked_project/spec/meta_magic_spec.rb +0 -10
  103. data/test/faked_project/spec/some_class_spec.rb +0 -10
  104. data/test/faked_project/spec/spec_helper.rb +0 -15
  105. data/test/faked_project/test/faked_test.rb +0 -11
  106. data/test/faked_project/test/meta_magic_test.rb +0 -13
  107. data/test/faked_project/test/some_class_test.rb +0 -15
  108. data/test/faked_project/test/test_helper.rb +0 -16
  109. data/test/fixtures/app/controllers/sample_controller.rb +0 -10
  110. data/test/fixtures/app/models/user.rb +0 -10
  111. data/test/fixtures/deleted_source_sample.rb +0 -15
  112. data/test/fixtures/frameworks/rspec_bad.rb +0 -9
  113. data/test/fixtures/frameworks/rspec_good.rb +0 -9
  114. data/test/fixtures/frameworks/testunit_bad.rb +0 -9
  115. data/test/fixtures/frameworks/testunit_good.rb +0 -9
  116. data/test/fixtures/resultset1.rb +0 -4
  117. data/test/fixtures/resultset2.rb +0 -5
  118. data/test/fixtures/sample.rb +0 -16
  119. data/test/fixtures/utf-8.rb +0 -3
  120. data/test/helper.rb +0 -35
  121. data/test/shoulda_macros.rb +0 -29
  122. data/test/test_1_8_fallbacks.rb +0 -33
  123. data/test/test_command_guesser.rb +0 -21
  124. data/test/test_deleted_source.rb +0 -16
  125. data/test/test_file_list.rb +0 -24
  126. data/test/test_filters.rb +0 -80
  127. data/test/test_merge_helpers.rb +0 -107
  128. data/test/test_result.rb +0 -147
  129. data/test/test_return_codes.rb +0 -39
  130. data/test/test_source_file.rb +0 -86
  131. data/test/test_source_file_line.rb +0 -110
@@ -1,80 +1,194 @@
1
- require 'multi_json'
2
-
3
- #
4
- # Singleton that is responsible for caching, loading and merging
5
- # SimpleCov::Results into a single result for coverage analysis based
6
- # upon multiple test suites.
7
- #
8
- module SimpleCov::ResultMerger
9
- class << self
10
- # The path to the resultset.yml cache file
11
- def resultset_path
12
- File.join(SimpleCov.coverage_path, '.resultset.json')
13
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module SimpleCov
6
+ #
7
+ # Singleton that is responsible for caching, loading and merging
8
+ # SimpleCov::Results into a single result for coverage analysis based
9
+ # upon multiple test suites.
10
+ #
11
+ module ResultMerger
12
+ class << self
13
+ # The path to the .resultset.json cache file
14
+ def resultset_path
15
+ File.join(SimpleCov.coverage_path, ".resultset.json")
16
+ end
17
+
18
+ def resultset_writelock
19
+ File.join(SimpleCov.coverage_path, ".resultset.json.lock")
20
+ end
21
+
22
+ def merge_and_store(*file_paths, ignore_timeout: false)
23
+ result = merge_results(*file_paths, ignore_timeout: ignore_timeout)
24
+ store_result(result) if result
25
+ result
26
+ end
27
+
28
+ def merge_results(*file_paths, ignore_timeout: false)
29
+ # It is intentional here that files are only read in and parsed one at a time.
30
+ #
31
+ # In big CI setups you might deal with 100s of CI jobs and each one producing Megabytes
32
+ # of data. Reading them all in easily produces Gigabytes of memory consumption which
33
+ # we want to avoid.
34
+ #
35
+ # For similar reasons a SimpleCov::Result is only created in the end as that'd create
36
+ # even more data especially when it also reads in all source files.
37
+ initial_memo = valid_results(file_paths.shift, ignore_timeout: ignore_timeout)
38
+
39
+ command_names, coverage = file_paths.reduce(initial_memo) do |memo, file_path|
40
+ merge_coverage(memo, valid_results(file_path, ignore_timeout: ignore_timeout))
41
+ end
42
+
43
+ create_result(command_names, coverage)
44
+ end
45
+
46
+ def valid_results(file_path, ignore_timeout: false)
47
+ results = parse_file(file_path)
48
+ merge_valid_results(results, ignore_timeout: ignore_timeout)
49
+ end
14
50
 
15
- # Loads the cached resultset from YAML and returns it as a Hash
16
- def resultset
17
- if stored_data
18
- MultiJson.decode(stored_data)
19
- else
51
+ def parse_file(path)
52
+ data = read_file(path)
53
+ parse_json(data)
54
+ end
55
+
56
+ def read_file(path)
57
+ return unless File.exist?(path)
58
+
59
+ data = File.read(path)
60
+ return if data.nil? || data.length < 2
61
+
62
+ data
63
+ end
64
+
65
+ def parse_json(content)
66
+ return {} unless content
67
+
68
+ JSON.parse(content) || {}
69
+ rescue StandardError
70
+ warn "[SimpleCov]: Warning! Parsing JSON content of resultset file failed"
20
71
  {}
21
72
  end
22
- end
23
73
 
24
- # Returns the contents of the resultset cache as a string or if the file is missing or empty nil
25
- def stored_data
26
- if File.exist?(resultset_path) and stored_data = File.read(resultset_path) and stored_data.length >= 2
27
- stored_data
28
- else
29
- nil
74
+ def merge_valid_results(results, ignore_timeout: false)
75
+ results = results.select { |_command_name, data| within_merge_timeout?(data) } unless ignore_timeout
76
+
77
+ command_plus_coverage = results.map do |command_name, data|
78
+ [[command_name], adapt_result(data.fetch("coverage"))]
79
+ end
80
+
81
+ # one file itself _might_ include multiple test runs
82
+ merge_coverage(*command_plus_coverage)
83
+ end
84
+
85
+ def within_merge_timeout?(data)
86
+ time_since_result_creation(data) < SimpleCov.merge_timeout
30
87
  end
31
- end
32
88
 
33
- # Gets the resultset hash and re-creates all included instances
34
- # of SimpleCov::Result from that.
35
- # All results that are above the SimpleCov.merge_timeout will be
36
- # dropped. Returns an array of SimpleCov::Result items.
37
- def results
38
- results = []
39
- resultset.each do |command_name, data|
40
- result = SimpleCov::Result.from_hash(command_name => data)
41
- # Only add result if the timeout is above the configured threshold
42
- if (Time.now - result.created_at) < SimpleCov.merge_timeout
43
- results << result
89
+ def time_since_result_creation(data)
90
+ Time.now - Time.at(data.fetch("timestamp"))
91
+ end
92
+
93
+ def create_result(command_names, coverage)
94
+ return nil unless coverage
95
+
96
+ command_name = command_names.reject(&:empty?).sort.join(", ")
97
+ SimpleCov::Result.new(coverage, command_name: command_name)
98
+ end
99
+
100
+ def merge_coverage(*results)
101
+ return [[""], nil] if results.empty?
102
+ return results.first if results.size == 1
103
+
104
+ results.reduce do |(memo_command, memo_coverage), (command, coverage)|
105
+ # timestamp is dropped here, which is intentional (we merge it, it gets a new time stamp as of now)
106
+ merged_coverage = Combine.combine(Combine::ResultsCombiner, memo_coverage, coverage)
107
+ merged_command = memo_command + command
108
+
109
+ [merged_command, merged_coverage]
44
110
  end
45
111
  end
46
- results
47
- end
48
112
 
49
- #
50
- # Gets all SimpleCov::Results from cache, merges them and produces a new
51
- # SimpleCov::Result with merged coverage data and the command_name
52
- # for the result consisting of a join on all source result's names
53
- #
54
- def merged_result
55
- merged = {}
56
- results.each do |result|
57
- merged = result.original_result.merge_resultset(merged)
58
- end
59
- result = SimpleCov::Result.new(merged)
60
- # Specify the command name
61
- result.command_name = results.map(&:command_name).sort.join(", ")
62
- result
63
- end
113
+ #
114
+ # Gets all SimpleCov::Results stored in resultset, merges them and produces a new
115
+ # SimpleCov::Result with merged coverage data and the command_name
116
+ # for the result consisting of a join on all source result's names
117
+ def merged_result
118
+ # conceptually this is just doing `merge_results(resultset_path)`
119
+ # it's more involved to make syre `synchronize_resultset` is only used around reading
120
+ resultset_hash = read_resultset
121
+ command_names, coverage = merge_valid_results(resultset_hash)
122
+
123
+ create_result(command_names, coverage)
124
+ end
125
+
126
+ def read_resultset
127
+ resultset_content =
128
+ synchronize_resultset do
129
+ read_file(resultset_path)
130
+ end
131
+
132
+ parse_json(resultset_content)
133
+ end
64
134
 
65
- # Saves the given SimpleCov::Result in the resultset cache
66
- def store_result(result)
67
- new_set = resultset
68
- command_name, data = result.to_hash.first
69
- new_set[command_name] = data
70
- File.open(resultset_path, "w+") do |f|
71
- if defined? ::JSON
72
- f.puts JSON.pretty_generate(new_set)
135
+ # Saves the given SimpleCov::Result in the resultset cache
136
+ def store_result(result)
137
+ synchronize_resultset do
138
+ # Ensure we have the latest, in case it was already cached
139
+ new_resultset = read_resultset
140
+
141
+ # A single result only ever has one command_name, see `SimpleCov::Result#to_hash`
142
+ command_name, data = result.to_hash.first
143
+ new_resultset[command_name] = data
144
+ File.open(resultset_path, "w+") do |f_|
145
+ f_.puts JSON.pretty_generate(new_resultset)
146
+ end
147
+ end
148
+ true
149
+ end
150
+
151
+ # Ensure only one process is reading or writing the resultset at any
152
+ # given time
153
+ def synchronize_resultset
154
+ # make it reentrant
155
+ return yield if defined?(@resultset_locked) && @resultset_locked
156
+
157
+ begin
158
+ @resultset_locked = true
159
+ File.open(resultset_writelock, "w+") do |f|
160
+ f.flock(File::LOCK_EX)
161
+ yield
162
+ end
163
+ ensure
164
+ @resultset_locked = false
165
+ end
166
+ end
167
+
168
+ # We changed the format of the raw result data in simplecov, as people are likely
169
+ # to have "old" resultsets lying around (but not too old so that they're still
170
+ # considered we can adapt them).
171
+ # See https://github.com/simplecov-ruby/simplecov/pull/824#issuecomment-576049747
172
+ def adapt_result(result)
173
+ if pre_simplecov_0_18_result?(result)
174
+ adapt_pre_simplecov_0_18_result(result)
73
175
  else
74
- f.puts MultiJson.encode(new_set)
176
+ result
177
+ end
178
+ end
179
+
180
+ # pre 0.18 coverage data pointed from file directly to an array of line coverage
181
+ def pre_simplecov_0_18_result?(result)
182
+ _key, data = result.first
183
+
184
+ data.is_a?(Array)
185
+ end
186
+
187
+ def adapt_pre_simplecov_0_18_result(result)
188
+ result.transform_values do |line_coverage_data|
189
+ {"lines" => line_coverage_data}
75
190
  end
76
191
  end
77
- true
78
192
  end
79
193
  end
80
194
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ #
5
+ # Responsible for producing file coverage metrics.
6
+ #
7
+ module SimulateCoverage
8
+ module_function
9
+
10
+ #
11
+ # Simulate normal file coverage report on
12
+ # ruby 2.5 and return similar hash with lines and branches keys
13
+ #
14
+ # Happens when a file wasn't required but still tracked.
15
+ #
16
+ # @return [Hash]
17
+ #
18
+ def call(absolute_path)
19
+ lines = File.foreach(absolute_path)
20
+
21
+ {
22
+ "lines" => LinesClassifier.new.classify(lines),
23
+ # we don't want to parse branches ourselves...
24
+ # requiring files can have side effects and we don't want to trigger that
25
+ "branches" => {}
26
+ }
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class SourceFile
5
+ #
6
+ # Representing single branch that has been detected in coverage report.
7
+ # Give us support methods that handle needed calculations.
8
+ class Branch
9
+ attr_reader :start_line, :end_line, :coverage, :type
10
+
11
+ # rubocop:disable Metrics/ParameterLists
12
+ def initialize(start_line:, end_line:, coverage:, inline:, type:)
13
+ @start_line = start_line
14
+ @end_line = end_line
15
+ @coverage = coverage
16
+ @inline = inline
17
+ @type = type
18
+ @skipped = false
19
+ end
20
+ # rubocop:enable Metrics/ParameterLists
21
+
22
+ def inline?
23
+ @inline
24
+ end
25
+
26
+ #
27
+ # Return true if there is relevant count defined > 0
28
+ #
29
+ # @return [Boolean]
30
+ #
31
+ def covered?
32
+ !skipped? && coverage.positive?
33
+ end
34
+
35
+ #
36
+ # Check if branche missed or not
37
+ #
38
+ # @return [Boolean]
39
+ #
40
+ def missed?
41
+ !skipped? && coverage.zero?
42
+ end
43
+
44
+ # The line on which we want to report the coverage
45
+ #
46
+ # Usually we choose the line above the start of the branch (so that it shows up
47
+ # at if/else) because that
48
+ # * highlights the condition
49
+ # * makes it distinguishable if the first line of the branch is an inline branch
50
+ # (see the nested_branches fixture)
51
+ #
52
+ def report_line
53
+ if inline?
54
+ start_line
55
+ else
56
+ start_line - 1
57
+ end
58
+ end
59
+
60
+ # Flags the branch as skipped
61
+ def skipped!
62
+ @skipped = true
63
+ end
64
+
65
+ # Returns true if the branch was marked skipped by virtue of nocov comments.
66
+ def skipped?
67
+ @skipped
68
+ end
69
+
70
+ def overlaps_with?(line_range)
71
+ start_line <= line_range.end && end_line >= line_range.begin
72
+ end
73
+
74
+ #
75
+ # Return array with coverage count and badge
76
+ #
77
+ # @return [Array]
78
+ #
79
+ def report
80
+ [type, coverage]
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class SourceFile
5
+ # Representation of a single line in a source file including
6
+ # this specific line's source code, line_number and code coverage,
7
+ # with the coverage being either nil (coverage not applicable, e.g. comment
8
+ # line), 0 (line not covered) or >1 (the amount of times the line was
9
+ # executed)
10
+ class Line
11
+ # The source code for this line. Aliased as :source
12
+ attr_reader :src
13
+ # The line number in the source file. Aliased as :line, :number
14
+ attr_reader :line_number
15
+ # The coverage data for this line: either nil (never), 0 (missed) or >=1 (times covered)
16
+ attr_reader :coverage
17
+ # Whether this line was skipped
18
+ attr_reader :skipped
19
+
20
+ # Lets grab some fancy aliases, shall we?
21
+ alias source src
22
+ alias line line_number
23
+ alias number line_number
24
+
25
+ def initialize(src, line_number, coverage)
26
+ raise ArgumentError, "Only String accepted for source" unless src.is_a?(String)
27
+ raise ArgumentError, "Only Integer accepted for line_number" unless line_number.is_a?(Integer)
28
+ raise ArgumentError, "Only Integer and nil accepted for coverage" unless coverage.is_a?(Integer) || coverage.nil?
29
+
30
+ @src = src
31
+ @line_number = line_number
32
+ @coverage = coverage
33
+ @skipped = false
34
+ end
35
+
36
+ # Returns true if this is a line that should have been covered, but was not
37
+ def missed?
38
+ !never? && !skipped? && coverage.zero?
39
+ end
40
+
41
+ # Returns true if this is a line that has been covered
42
+ def covered?
43
+ !never? && !skipped? && coverage.positive?
44
+ end
45
+
46
+ # Returns true if this line is not relevant for coverage
47
+ def never?
48
+ !skipped? && coverage.nil?
49
+ end
50
+
51
+ # Flags this line as skipped
52
+ def skipped!
53
+ @skipped = true
54
+ end
55
+
56
+ # Returns true if this line was skipped, false otherwise. Lines are skipped if they are wrapped with
57
+ # # :nocov: comment lines.
58
+ def skipped?
59
+ skipped
60
+ end
61
+
62
+ # The status of this line - either covered, missed, skipped or never. Useful i.e. for direct use
63
+ # as a css class in report generation
64
+ def status
65
+ return "skipped" if skipped?
66
+ return "never" if never?
67
+ return "missed" if missed?
68
+ return "covered" if covered?
69
+ end
70
+ end
71
+ end
72
+ end