simplecov 0.8.0.pre2 → 0.18.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +368 -27
  3. data/CODE_OF_CONDUCT.md +76 -0
  4. data/CONTRIBUTING.md +38 -7
  5. data/ISSUE_TEMPLATE.md +23 -0
  6. data/{MIT-LICENSE → LICENSE} +1 -1
  7. data/README.md +449 -230
  8. data/doc/alternate-formatters.md +61 -0
  9. data/doc/commercial-services.md +20 -0
  10. data/doc/editor-integration.md +18 -0
  11. data/lib/simplecov.rb +294 -59
  12. data/lib/simplecov/combine.rb +30 -0
  13. data/lib/simplecov/combine/branches_combiner.rb +32 -0
  14. data/lib/simplecov/combine/files_combiner.rb +25 -0
  15. data/lib/simplecov/combine/lines_combiner.rb +43 -0
  16. data/lib/simplecov/combine/results_combiner.rb +60 -0
  17. data/lib/simplecov/command_guesser.rb +46 -40
  18. data/lib/simplecov/configuration.rb +346 -221
  19. data/lib/simplecov/defaults.rb +35 -75
  20. data/lib/simplecov/exit_codes.rb +9 -4
  21. data/lib/simplecov/file_list.rb +80 -36
  22. data/lib/simplecov/filter.rb +51 -3
  23. data/lib/simplecov/formatter.rb +4 -2
  24. data/lib/simplecov/formatter/multi_formatter.rb +28 -19
  25. data/lib/simplecov/formatter/simple_formatter.rb +21 -15
  26. data/lib/simplecov/last_run.rb +21 -13
  27. data/lib/simplecov/lines_classifier.rb +48 -0
  28. data/lib/simplecov/load_global_config.rb +8 -0
  29. data/lib/simplecov/no_defaults.rb +4 -2
  30. data/lib/simplecov/profiles.rb +29 -23
  31. data/lib/simplecov/profiles/bundler_filter.rb +5 -0
  32. data/lib/simplecov/profiles/hidden_filter.rb +5 -0
  33. data/lib/simplecov/profiles/rails.rb +18 -0
  34. data/lib/simplecov/profiles/root_filter.rb +10 -0
  35. data/lib/simplecov/profiles/test_frameworks.rb +8 -0
  36. data/lib/simplecov/result.rb +39 -68
  37. data/lib/simplecov/result_adapter.rb +30 -0
  38. data/lib/simplecov/result_merger.rb +110 -60
  39. data/lib/simplecov/simulate_coverage.rb +29 -0
  40. data/lib/simplecov/source_file.rb +261 -135
  41. data/lib/simplecov/source_file/branch.rb +106 -0
  42. data/lib/simplecov/source_file/line.rb +72 -0
  43. data/lib/simplecov/useless_results_remover.rb +16 -0
  44. data/lib/simplecov/version.rb +4 -2
  45. metadata +48 -197
  46. data/.gitignore +0 -32
  47. data/.travis.yml +0 -28
  48. data/.yardopts +0 -1
  49. data/Appraisals +0 -8
  50. data/Gemfile +0 -16
  51. data/Rakefile +0 -32
  52. data/cucumber.yml +0 -13
  53. data/features/config_autoload.feature +0 -46
  54. data/features/config_command_name.feature +0 -45
  55. data/features/config_coverage_dir.feature +0 -33
  56. data/features/config_deactivate_merging.feature +0 -42
  57. data/features/config_formatters.feature +0 -52
  58. data/features/config_merge_timeout.feature +0 -39
  59. data/features/config_nocov_token.feature +0 -79
  60. data/features/config_profiles.feature +0 -44
  61. data/features/config_project_name.feature +0 -27
  62. data/features/config_styles.feature +0 -121
  63. data/features/cucumber_basic.feature +0 -29
  64. data/features/maximum_coverage_drop.feature +0 -36
  65. data/features/merging_test_unit_and_rspec.feature +0 -44
  66. data/features/minimum_coverage.feature +0 -59
  67. data/features/refuse_coverage_drop.feature +0 -35
  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 -66
  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 -44
  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/gemfiles/multi_json_legacy.gemfile +0 -12
  87. data/gemfiles/multi_json_new.gemfile +0 -12
  88. data/lib/simplecov/jruby16_fix.rb +0 -43
  89. data/lib/simplecov/json.rb +0 -27
  90. data/lib/simplecov/merge_helpers.rb +0 -39
  91. data/lib/simplecov/railtie.rb +0 -7
  92. data/lib/simplecov/railties/tasks.rake +0 -11
  93. data/simplecov.gemspec +0 -30
  94. data/test/faked_project/Gemfile +0 -6
  95. data/test/faked_project/Rakefile +0 -8
  96. data/test/faked_project/cucumber.yml +0 -13
  97. data/test/faked_project/features/step_definitions/my_steps.rb +0 -23
  98. data/test/faked_project/features/support/env.rb +0 -12
  99. data/test/faked_project/features/test_stuff.feature +0 -6
  100. data/test/faked_project/lib/faked_project.rb +0 -11
  101. data/test/faked_project/lib/faked_project/framework_specific.rb +0 -18
  102. data/test/faked_project/lib/faked_project/meta_magic.rb +0 -24
  103. data/test/faked_project/lib/faked_project/some_class.rb +0 -29
  104. data/test/faked_project/spec/faked_spec.rb +0 -11
  105. data/test/faked_project/spec/meta_magic_spec.rb +0 -10
  106. data/test/faked_project/spec/some_class_spec.rb +0 -10
  107. data/test/faked_project/spec/spec_helper.rb +0 -15
  108. data/test/faked_project/test/faked_test.rb +0 -11
  109. data/test/faked_project/test/meta_magic_test.rb +0 -13
  110. data/test/faked_project/test/some_class_test.rb +0 -15
  111. data/test/faked_project/test/test_helper.rb +0 -16
  112. data/test/fixtures/app/controllers/sample_controller.rb +0 -10
  113. data/test/fixtures/app/models/user.rb +0 -10
  114. data/test/fixtures/deleted_source_sample.rb +0 -15
  115. data/test/fixtures/frameworks/rspec_bad.rb +0 -9
  116. data/test/fixtures/frameworks/rspec_good.rb +0 -9
  117. data/test/fixtures/frameworks/testunit_bad.rb +0 -9
  118. data/test/fixtures/frameworks/testunit_good.rb +0 -9
  119. data/test/fixtures/iso-8859.rb +0 -3
  120. data/test/fixtures/resultset1.rb +0 -4
  121. data/test/fixtures/resultset2.rb +0 -5
  122. data/test/fixtures/sample.rb +0 -16
  123. data/test/fixtures/utf-8.rb +0 -3
  124. data/test/helper.rb +0 -34
  125. data/test/shoulda_macros.rb +0 -19
  126. data/test/test_1_8_fallbacks.rb +0 -31
  127. data/test/test_command_guesser.rb +0 -19
  128. data/test/test_deleted_source.rb +0 -14
  129. data/test/test_file_list.rb +0 -22
  130. data/test/test_filters.rb +0 -78
  131. data/test/test_merge_helpers.rb +0 -105
  132. data/test/test_result.rb +0 -160
  133. data/test/test_return_codes.rb +0 -37
  134. data/test/test_source_file.rb +0 -106
  135. data/test/test_source_file_line.rb +0 -106
@@ -1,92 +1,52 @@
1
- # Load default formatter gem
2
- require 'simplecov-html'
3
-
4
- SimpleCov.profiles.define 'root_filter' do
5
- # Exclude all files outside of simplecov root
6
- add_filter do |src|
7
- !(src.filename =~ /^#{Regexp.escape(SimpleCov.root)}/)
8
- end
9
- end
10
-
11
- SimpleCov.profiles.define 'test_frameworks' do
12
- add_filter '/test/'
13
- add_filter '/features/'
14
- add_filter '/spec/'
15
- add_filter '/autotest/'
16
- end
1
+ # frozen_string_literal: true
17
2
 
18
- SimpleCov.profiles.define 'rails' do
19
- load_profile 'test_frameworks'
20
-
21
- add_filter '/config/'
22
- add_filter '/db/'
23
- add_filter '/vendor/bundle/'
24
-
25
- add_group 'Controllers', 'app/controllers'
26
- add_group 'Models', 'app/models'
27
- add_group 'Mailers', 'app/mailers'
28
- add_group 'Helpers', 'app/helpers'
29
- add_group 'Libraries', 'lib'
30
- add_group 'Plugins', 'vendor/plugins'
31
- end
3
+ # Load default formatter gem
4
+ require "simplecov-html"
5
+ require "pathname"
6
+ require "simplecov/profiles/root_filter"
7
+ require "simplecov/profiles/test_frameworks"
8
+ require "simplecov/profiles/bundler_filter"
9
+ require "simplecov/profiles/hidden_filter"
10
+ require "simplecov/profiles/rails"
32
11
 
33
12
  # Default configuration
34
13
  SimpleCov.configure do
35
14
  formatter SimpleCov::Formatter::HTMLFormatter
15
+ load_profile "bundler_filter"
16
+ load_profile "hidden_filter"
36
17
  # Exclude files outside of SimpleCov.root
37
- load_profile 'root_filter'
18
+ load_profile "root_filter"
38
19
  end
39
20
 
40
21
  # Gotta stash this a-s-a-p, see the CommandGuesser class and i.e. #110 for further info
41
- SimpleCov::CommandGuesser.original_run_command = "#{$0} #{ARGV.join(" ")}"
22
+ SimpleCov::CommandGuesser.original_run_command = "#{$PROGRAM_NAME} #{ARGV.join(' ')}"
42
23
 
43
24
  at_exit do
44
- # Store the exit status of the test run since it goes away after calling the at_exit proc...
45
- if $! #was an exception thrown?
46
- #if it was a SystemExit, use the accompanying status
47
- #otherwise set a non-zero status representing termination by some other exception
48
- #(see github issue 41)
49
- @exit_status = $!.is_a?(SystemExit) ? $!.status : SimpleCov::ExitCodes::EXCEPTION
50
- else
51
- @exit_status = 0
52
- end
53
-
54
- SimpleCov.at_exit.call
25
+ # If we are in a different process than called start, don't interfere.
26
+ next if SimpleCov.pid != Process.pid
55
27
 
56
- if SimpleCov.result? # Result has been computed
57
- covered_percent = SimpleCov.result.covered_percent.round(2)
58
-
59
- if @exit_status == 0 # No other errors
60
- @exit_status = if covered_percent < SimpleCov.minimum_coverage
61
- $stderr.puts "Coverage (%.2f%%) is below the expected minimum coverage (%.2f%%)." % \
62
- [covered_percent, SimpleCov.minimum_coverage]
63
-
64
- SimpleCov::ExitCodes::MINIMUM_COVERAGE
65
-
66
- elsif (last_run = SimpleCov::LastRun.read)
67
- diff = last_run['result']['covered_percent'] - covered_percent
68
- if diff > SimpleCov.maximum_coverage_drop
69
- $stderr.puts "Coverage has dropped by %.2f%% since the last time (maximum allowed: %.2f%%)." % \
70
- [diff, SimpleCov.maximum_coverage_drop]
71
-
72
- SimpleCov::ExitCodes::MAXIMUM_COVERAGE_DROP
73
- end
74
- end
75
- end
76
-
77
- metrics = {
78
- :result => { :covered_percent => covered_percent }
79
- }
80
- SimpleCov::LastRun.write(metrics)
81
- end
82
-
83
- exit @exit_status if @exit_status # Force exit with stored status (see github issue #5)
28
+ SimpleCov.set_exit_exception
29
+ SimpleCov.run_exit_tasks!
84
30
  end
85
31
 
86
32
  # Autoload config from ~/.simplecov if present
87
- global_config_path = File.join(File.expand_path("~"), '.simplecov')
88
- load global_config_path if File.exist?(global_config_path)
33
+ require "simplecov/load_global_config"
89
34
 
90
35
  # Autoload config from .simplecov if present
91
- config_path = File.join(SimpleCov.root, '.simplecov')
92
- load config_path if File.exist?(config_path)
36
+ # Recurse upwards until we find .simplecov or reach the root directory
37
+
38
+ config_path = Pathname.new(SimpleCov.root)
39
+ loop do
40
+ filename = config_path.join(".simplecov")
41
+ if filename.exist?
42
+ begin
43
+ load filename
44
+ rescue LoadError, StandardError
45
+ warn "Warning: Error occurred while trying to load #{filename}. " \
46
+ "Error message: #{$!.message}"
47
+ end
48
+ break
49
+ end
50
+ config_path, = config_path.split
51
+ break if config_path.root?
52
+ end
@@ -1,5 +1,10 @@
1
- module SimpleCov::ExitCodes
2
- EXCEPTION = 1
3
- MINIMUM_COVERAGE = 2
4
- MAXIMUM_COVERAGE_DROP = 3
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module ExitCodes
5
+ SUCCESS = 0
6
+ EXCEPTION = 1
7
+ MINIMUM_COVERAGE = 2
8
+ MAXIMUM_COVERAGE_DROP = 3
9
+ end
5
10
  end
@@ -1,44 +1,88 @@
1
- # An array of SimpleCov SourceFile instances with additional collection helper
2
- # methods for calculating coverage across them etc.
3
- class SimpleCov::FileList < Array
4
- # Returns the count of lines that have coverage
5
- def covered_lines
6
- return 0.0 if empty?
7
- map {|f| f.covered_lines.count }.inject(&:+)
8
- end
1
+ # frozen_string_literal: true
9
2
 
10
- # Returns the count of lines that have been missed
11
- def missed_lines
12
- return 0.0 if empty?
13
- map {|f| f.missed_lines.count }.inject(&:+)
14
- end
3
+ module SimpleCov
4
+ # An array of SimpleCov SourceFile instances with additional collection helper
5
+ # methods for calculating coverage across them etc.
6
+ class FileList < Array
7
+ # Returns the count of lines that have coverage
8
+ def covered_lines
9
+ return 0.0 if empty?
15
10
 
16
- # Returns the count of lines that are not relevant for coverage
17
- def never_lines
18
- return 0.0 if empty?
19
- map {|f| f.never_lines.count }.inject(&:+)
20
- end
11
+ map { |f| f.covered_lines.count }.inject(:+)
12
+ end
21
13
 
22
- # Returns the count of skipped lines
23
- def skipped_lines
24
- return 0.0 if empty?
25
- map {|f| f.skipped_lines.count }.inject(&:+)
26
- end
14
+ # Returns the count of lines that have been missed
15
+ def missed_lines
16
+ return 0.0 if empty?
27
17
 
28
- # Returns the overall amount of relevant lines of code across all files in this list
29
- def lines_of_code
30
- covered_lines + missed_lines
31
- end
18
+ map { |f| f.missed_lines.count }.inject(:+)
19
+ end
32
20
 
33
- # Computes the coverage based upon lines covered and lines missed
34
- def covered_percent
35
- return 100.0 if empty? or lines_of_code == 0
36
- covered_lines * 100.0 / lines_of_code
37
- end
21
+ # Returns the count of lines that are not relevant for coverage
22
+ def never_lines
23
+ return 0.0 if empty?
24
+
25
+ map { |f| f.never_lines.count }.inject(:+)
26
+ end
27
+
28
+ # Returns the count of skipped lines
29
+ def skipped_lines
30
+ return 0.0 if empty?
31
+
32
+ map { |f| f.skipped_lines.count }.inject(:+)
33
+ end
34
+
35
+ # Computes the coverage based upon lines covered and lines missed for each file
36
+ # Returns an array with all coverage percentages
37
+ def covered_percentages
38
+ map(&:covered_percent)
39
+ end
40
+
41
+ # Finds the least covered file and returns that file's name
42
+ def least_covered_file
43
+ min_by(&:covered_percent).filename
44
+ end
45
+
46
+ # Returns the overall amount of relevant lines of code across all files in this list
47
+ def lines_of_code
48
+ covered_lines + missed_lines
49
+ end
50
+
51
+ # Computes the coverage based upon lines covered and lines missed
52
+ # @return [Float]
53
+ def covered_percent
54
+ return 100.0 if empty? || lines_of_code.zero?
55
+
56
+ Float(covered_lines * 100.0 / lines_of_code)
57
+ end
58
+
59
+ # Computes the strength (hits / line) based upon lines covered and lines missed
60
+ # @return [Float]
61
+ def covered_strength
62
+ return 0.0 if empty? || lines_of_code.zero?
63
+
64
+ Float(map { |f| f.covered_strength * f.lines_of_code }.inject(:+) / lines_of_code)
65
+ end
66
+
67
+ # Return total count of branches in all files
68
+ def total_branches
69
+ return 0 if empty?
70
+
71
+ map { |file| file.total_branches.count }.inject(:+)
72
+ end
73
+
74
+ # Return total count of covered branches
75
+ def covered_branches
76
+ return 0 if empty?
77
+
78
+ map { |file| file.covered_branches.count }.inject(:+)
79
+ end
80
+
81
+ # Return total count of covered branches
82
+ def missed_branches
83
+ return 0 if empty?
38
84
 
39
- # Computes the strength (hits / line) based upon lines covered and lines missed
40
- def covered_strength
41
- return 0 if empty? or lines_of_code == 0
42
- map {|f| f.covered_strength }.inject(&:+).to_f / size
85
+ map { |file| file.missed_branches.count }.inject(:+)
86
+ end
43
87
  end
44
88
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SimpleCov
2
4
  #
3
5
  # Base filter class. Inherit from this to create custom filters,
@@ -16,21 +18,49 @@ module SimpleCov
16
18
  @filter_argument = filter_argument
17
19
  end
18
20
 
19
- def matches?(source_file)
21
+ def matches?(_source_file)
20
22
  raise "The base filter class is not intended for direct use"
21
23
  end
22
24
 
23
25
  def passes?(source_file)
24
- warn "DEPRECATION: SimpleCov::Filter#passes?(x) has been renamed to #matches?. Please update your custom filters accordingly!"
26
+ warn "#{Kernel.caller.first}: [DEPRECATION] #passes? is deprecated. Use #matches? instead."
25
27
  matches?(source_file)
26
28
  end
29
+
30
+ def self.build_filter(filter_argument)
31
+ return filter_argument if filter_argument.is_a?(SimpleCov::Filter)
32
+
33
+ class_for_argument(filter_argument).new(filter_argument)
34
+ end
35
+
36
+ def self.class_for_argument(filter_argument)
37
+ if filter_argument.is_a?(String)
38
+ SimpleCov::StringFilter
39
+ elsif filter_argument.is_a?(Regexp)
40
+ SimpleCov::RegexFilter
41
+ elsif filter_argument.is_a?(Array)
42
+ SimpleCov::ArrayFilter
43
+ elsif filter_argument.is_a?(Proc)
44
+ SimpleCov::BlockFilter
45
+ else
46
+ raise ArgumentError, "You have provided an unrecognized filter type"
47
+ end
48
+ end
27
49
  end
28
50
 
29
51
  class StringFilter < SimpleCov::Filter
30
52
  # Returns true when the given source file's filename matches the
31
53
  # string configured when initializing this Filter with StringFilter.new('somestring)
32
54
  def matches?(source_file)
33
- (source_file.filename =~ /#{filter_argument}/)
55
+ source_file.project_filename.include?(filter_argument)
56
+ end
57
+ end
58
+
59
+ class RegexFilter < SimpleCov::Filter
60
+ # Returns true when the given source file's filename matches the
61
+ # regex configured when initializing this Filter with RegexFilter.new(/someregex/)
62
+ def matches?(source_file)
63
+ (source_file.project_filename =~ filter_argument)
34
64
  end
35
65
  end
36
66
 
@@ -41,4 +71,22 @@ module SimpleCov
41
71
  filter_argument.call(source_file)
42
72
  end
43
73
  end
74
+
75
+ class ArrayFilter < SimpleCov::Filter
76
+ def initialize(filter_argument)
77
+ filter_objects = filter_argument.map do |arg|
78
+ Filter.build_filter(arg)
79
+ end
80
+
81
+ super(filter_objects)
82
+ end
83
+
84
+ # Returns true if any of the filters in the array match the given source file.
85
+ # Configure this Filter like StringFilter.new(['some/path', /^some_regex/, Proc.new {|src_file| ... }])
86
+ def matches?(source_files_list)
87
+ filter_argument.any? do |arg|
88
+ arg.matches?(source_files_list)
89
+ end
90
+ end
91
+ end
44
92
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SimpleCov
2
4
  # TODO: Documentation on how to build your own formatters
3
5
  module Formatter
4
6
  end
5
7
  end
6
8
 
7
- require 'simplecov/formatter/simple_formatter'
8
- require 'simplecov/formatter/multi_formatter'
9
+ require "simplecov/formatter/simple_formatter"
10
+ require "simplecov/formatter/multi_formatter"
@@ -1,25 +1,34 @@
1
- class SimpleCov::Formatter::MultiFormatter
2
- def self.[](*args)
3
- Class.new(self) do
4
- define_method :formatters do
5
- @formatters ||= args
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Formatter
5
+ class MultiFormatter
6
+ module InstanceMethods
7
+ def format(result)
8
+ formatters.map do |formatter|
9
+ begin
10
+ formatter.new.format(result)
11
+ rescue StandardError => e
12
+ warn("Formatter #{formatter} failed with #{e.class}: #{e.message} (#{e.backtrace.first})")
13
+ nil
14
+ end
15
+ end
16
+ end
6
17
  end
7
- end
8
- end
9
18
 
10
- def format(result)
11
- formatters.map do |formatter|
12
- begin
13
- formatter.new.format(result)
14
- rescue => e
15
- STDERR.puts("Formatter #{formatter} failed with #{e.class}: #{e.message} (#{e.backtrace.first})")
16
- nil
19
+ def self.new(formatters = nil)
20
+ Class.new do
21
+ define_method :formatters do
22
+ @formatters ||= Array(formatters)
23
+ end
24
+ include InstanceMethods
25
+ end
17
26
  end
18
- end
19
- end
20
27
 
21
- def formatters
22
- @formatters ||= []
28
+ def self.[](*args)
29
+ warn "#{Kernel.caller.first}: [DEPRECATION] ::[] is deprecated. Use ::new instead."
30
+ new(Array([*args]))
31
+ end
32
+ end
23
33
  end
24
-
25
34
  end
@@ -1,19 +1,25 @@
1
- #
2
- # A ridiculously simple formatter for SimpleCov results.
3
- #
4
- class SimpleCov::Formatter::SimpleFormatter
5
- # Takes a SimpleCov::Result and generates a string out of it
6
- def format(result)
7
- output = ""
8
- result.groups.each do |name, files|
9
- output << "Group: #{name}\n"
10
- output << "="*40
11
- output << "\n"
12
- files.each do |file|
13
- output << "#{file.filename} (coverage: #{file.covered_percent.round(2)}%)\n"
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
+ output = +""
12
+ result.groups.each do |name, files|
13
+ output << "Group: #{name}\n"
14
+ output << "=" * 40
15
+ output << "\n"
16
+ files.each do |file|
17
+ output << "#{file.filename} (coverage: #{file.covered_percent.round(2)}%)\n"
18
+ end
19
+ output << "\n"
20
+ end
21
+ output
14
22
  end
15
- output << "\n"
16
23
  end
17
- output
18
24
  end
19
25
  end