diff --git a/Gemfile.lock b/Gemfile.lock index 799664e4..938b7e88 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -12,7 +12,6 @@ PATH simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) - simplecov_json_formatter (~> 0.1) GEM remote: https://rubygems.org/ @@ -142,7 +141,6 @@ GEM parser (>= 3.3.1.0) ruby-progressbar (1.13.0) simplecov-html (0.13.1) - simplecov_json_formatter (0.1.4) spoon (0.0.6) ffi sys-uname (1.3.1) @@ -192,4 +190,4 @@ DEPENDENCIES webrick BUNDLED WITH - 2.7.0.dev + 2.6.9 diff --git a/README.md b/README.md index a22b17c6..c62c8301 100644 --- a/README.md +++ b/README.md @@ -869,18 +869,17 @@ SimpleCov.formatters = [ ## JSON formatter -SimpleCov is packaged with a separate gem called [simplecov_json_formatter](https://github.com/codeclimate-community/simplecov_json_formatter) that provides you with a JSON formatter, this formatter could be useful for different use cases, such as for CI consumption or for reporting to external services. - -In order to use it you will need to manually load the installed gem like so: +SimpleCov is packaged with a `SimpleCov::Formatter::JSONFormatter` that provides you with a JSON formatter, this formatter could be useful for different use cases, such as for CI consumption or for reporting to external services. ```ruby -require "simplecov_json_formatter" SimpleCov.formatter = SimpleCov::Formatter::JSONFormatter ``` > _Note:_ In case you plan to report your coverage results to CodeClimate services, know that SimpleCov will automatically use the > JSON formatter along with the HTML formatter when the `CC_TEST_REPORTER_ID` variable is present in the environment. +> This exporter was originally separate [simplecov_json_formatter](https://github.com/codeclimate-community/simplecov_json_formatter) gem and it was needed to require manually using `require 'simplecov_json_formatter'`. Currently it is loaded by default. + ## Available formatters, editor integrations and hosted services * [Open Source formatter and integration plugins for SimpleCov](doc/alternate-formatters.md) diff --git a/features/config_json_formatter.feature b/features/config_json_formatter.feature index ed7a4473..6d0ab9c6 100644 --- a/features/config_json_formatter.feature +++ b/features/config_json_formatter.feature @@ -12,7 +12,6 @@ Feature: Given SimpleCov for Test/Unit is configured with: """ require 'simplecov' - require 'simplecov_json_formatter' SimpleCov.formatter = SimpleCov::Formatter::JSONFormatter SimpleCov.at_exit do puts SimpleCov.result.format! diff --git a/lib/simplecov/default_formatter.rb b/lib/simplecov/default_formatter.rb index 547d1578..f48a2247 100644 --- a/lib/simplecov/default_formatter.rb +++ b/lib/simplecov/default_formatter.rb @@ -8,10 +8,7 @@ def from_env(env) formatters = [SimpleCov::Formatter::HTMLFormatter] # When running under a CI that uses CodeClimate, JSON output is expected - if env.fetch("CC_TEST_REPORTER_ID", nil) - require "simplecov_json_formatter" - formatters.push(SimpleCov::Formatter::JSONFormatter) - end + formatters.push(SimpleCov::Formatter::JSONFormatter) if env.fetch("CC_TEST_REPORTER_ID", nil) formatters end diff --git a/lib/simplecov/formatter.rb b/lib/simplecov/formatter.rb index c5f9eae3..cf9728ff 100644 --- a/lib/simplecov/formatter.rb +++ b/lib/simplecov/formatter.rb @@ -8,3 +8,4 @@ module Formatter require_relative "formatter/simple_formatter" require_relative "formatter/multi_formatter" +require_relative "formatter/json_formatter" diff --git a/lib/simplecov/formatter/json_formatter.rb b/lib/simplecov/formatter/json_formatter.rb new file mode 100644 index 00000000..48cb6a33 --- /dev/null +++ b/lib/simplecov/formatter/json_formatter.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require_relative "json_formatter/result_hash_formatter" +require_relative "json_formatter/result_exporter" +require "json" + +module SimpleCov + module Formatter + class JSONFormatter + def format(result, verbose: true) + result_hash = format_result(result) + + export_formatted_result(result_hash) + + puts output_message(result) if verbose + end + + private + + def format_result(result) + result_hash_formater = ResultHashFormatter.new(result) + result_hash_formater.format + end + + def export_formatted_result(result_hash) + result_exporter = ResultExporter.new(result_hash) + result_exporter.export + end + + def output_message(result) + "JSON Coverage report generated for #{result.command_name} to #{SimpleCov.coverage_path}. " \ + "#{result.covered_lines} / #{result.total_lines} LOC (#{result.covered_percent.round(2)}%) covered." + end + end + end +end diff --git a/lib/simplecov/formatter/json_formatter/result_exporter.rb b/lib/simplecov/formatter/json_formatter/result_exporter.rb new file mode 100644 index 00000000..7e98e954 --- /dev/null +++ b/lib/simplecov/formatter/json_formatter/result_exporter.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module SimpleCov + module Formatter + class JSONFormatter + class ResultExporter + FILENAME = "coverage.json" + + def initialize(result_hash) + @result = result_hash + end + + def export + File.open(export_path, "w") do |file| + file << json_result + end + end + + private + + def json_result + JSON.pretty_generate(@result) + end + + def export_path + File.join(SimpleCov.coverage_path, FILENAME) + end + end + end + end +end diff --git a/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb b/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb new file mode 100644 index 00000000..3e92c2d9 --- /dev/null +++ b/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require_relative "source_file_formatter" + +module SimpleCov + module Formatter + class JSONFormatter + class ResultHashFormatter + def initialize(result) + @result = result + end + + def format + format_files + format_groups + + formatted_result + end + + private + + def format_files + @result.files.each do |source_file| + formatted_result[:coverage][source_file.filename] = + format_source_file(source_file) + end + end + + def format_groups + @result.groups.each do |name, file_list| + formatted_result[:groups][name] = { + lines: { + covered_percent: file_list.covered_percent + } + } + end + end + + def formatted_result + @formatted_result ||= { + meta: { + simplecov_version: SimpleCov::VERSION + }, + coverage: {}, + groups: {} + } + end + + def format_source_file(source_file) + source_file_formatter = SourceFileFormatter.new(source_file) + source_file_formatter.format + end + end + end + end +end diff --git a/lib/simplecov/formatter/json_formatter/source_file_formatter.rb b/lib/simplecov/formatter/json_formatter/source_file_formatter.rb new file mode 100644 index 00000000..67303e4b --- /dev/null +++ b/lib/simplecov/formatter/json_formatter/source_file_formatter.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module SimpleCov + module Formatter + class JSONFormatter + class SourceFileFormatter + def initialize(source_file) + @source_file = source_file + @line_coverage = nil + end + + def format + if SimpleCov.branch_coverage? + line_coverage.merge(branch_coverage) + else + line_coverage + end + end + + private + + def line_coverage + @line_coverage ||= { + lines: lines + } + end + + def branch_coverage + { + branches: branches + } + end + + def lines + @source_file.lines.collect do |line| + parse_line(line) + end + end + + def branches + @source_file.branches.collect do |branch| + parse_branch(branch) + end + end + + def parse_line(line) + return line.coverage unless line.skipped? + + "ignored" + end + + def parse_branch(branch) + { + type: branch.type, + start_line: branch.start_line, + end_line: branch.end_line, + coverage: parse_line(branch) + } + end + end + end + end +end diff --git a/simplecov.gemspec b/simplecov.gemspec index 21f35eeb..a0dec3cb 100644 --- a/simplecov.gemspec +++ b/simplecov.gemspec @@ -37,7 +37,6 @@ Gem::Specification.new do |gem| gem.add_dependency "docile", "~> 1.1" gem.add_dependency "simplecov-html", "~> 0.11" - gem.add_dependency "simplecov_json_formatter", "~> 0.1" gem.files = Dir["{lib}/**/*.*", "bin/*", "LICENSE", "CHANGELOG.md", "README.md", "doc/*"] gem.require_paths = ["lib"] diff --git a/spec/default_formatter_spec.rb b/spec/default_formatter_spec.rb index 9f2e4be8..0a6e005e 100644 --- a/spec/default_formatter_spec.rb +++ b/spec/default_formatter_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "helper" -require "simplecov_json_formatter" describe SimpleCov::Formatter do describe ".from_env" do diff --git a/spec/fixtures/json/sample.json b/spec/fixtures/json/sample.json new file mode 100644 index 00000000..55a4e7a0 --- /dev/null +++ b/spec/fixtures/json/sample.json @@ -0,0 +1,37 @@ +{ + "meta": { + "simplecov_version": "0.22.0" + }, + "coverage": { + "/STUB_WORKING_DIRECTORY/spec/fixtures/json/sample.rb": { + "lines": [ + null, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + 0, + null, + 1, + null, + null, + null, + "ignored", + "ignored", + "ignored", + "ignored", + "ignored", + null + ] + } + }, + "groups": {} +} diff --git a/spec/fixtures/json/sample.rb b/spec/fixtures/json/sample.rb new file mode 100644 index 00000000..83397c5a --- /dev/null +++ b/spec/fixtures/json/sample.rb @@ -0,0 +1,25 @@ +# Foo class +class Foo + def initialize + @foo = "bar" + @bar = "foo" + end + + def bar + @foo + end + + def foo(param) + if param + @bar + else + @foo + end + end + + # :nocov: + def skipped + @foo * 2 + end + # :nocov: +end diff --git a/spec/fixtures/json/sample_groups.json b/spec/fixtures/json/sample_groups.json new file mode 100644 index 00000000..1c47f59c --- /dev/null +++ b/spec/fixtures/json/sample_groups.json @@ -0,0 +1,43 @@ +{ + "meta": { + "simplecov_version": "0.22.0" + }, + "coverage": { + "/STUB_WORKING_DIRECTORY/spec/fixtures/json/sample.rb": { + "lines": [ + null, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + 0, + null, + 1, + null, + null, + null, + "ignored", + "ignored", + "ignored", + "ignored", + "ignored", + null + ] + } + }, + "groups": { + "My Group": { + "lines": { + "covered_percent": 80.0 + } + } + } +} diff --git a/spec/fixtures/json/sample_with_branch.json b/spec/fixtures/json/sample_with_branch.json new file mode 100644 index 00000000..ac451e80 --- /dev/null +++ b/spec/fixtures/json/sample_with_branch.json @@ -0,0 +1,51 @@ +{ + "meta": { + "simplecov_version": "0.22.0" + }, + "coverage": { + "/STUB_WORKING_DIRECTORY/spec/fixtures/json/sample.rb": { + "lines": [ + null, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + 0, + null, + 1, + null, + null, + null, + "ignored", + "ignored", + "ignored", + "ignored", + "ignored", + null + ], + "branches": [ + { + "type": "then", + "start_line": 14, + "end_line": 14, + "coverage": 0 + }, + { + "type": "else", + "start_line": 16, + "end_line": 16, + "coverage": 1 + } + ] + } + }, + "groups": {} +} diff --git a/spec/json_formatter_spec.rb b/spec/json_formatter_spec.rb new file mode 100644 index 00000000..5e4d43b4 --- /dev/null +++ b/spec/json_formatter_spec.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require "helper" + +require "simplecov/formatter/json_formatter" + +describe SimpleCov::Formatter::JSONFormatter do + let(:result) do + SimpleCov::Result.new({ + source_fixture("json/sample.rb") => {"lines" => [ + nil, 1, 1, 1, 1, nil, nil, 1, 1, nil, nil, + 1, 1, 0, nil, 1, nil, nil, nil, nil, 1, 0, nil, nil, nil + ]} + }) + end + + describe "format" do + context "with line coverage" do + it "works" do + subject.format(result, verbose: false) + expect(json_ouput).to eq(json_result("sample")) + end + end + + context "with branch coverage" do + let(:original_lines) do + [nil, 1, 1, 1, 1, nil, nil, 1, 1, + nil, nil, 1, 1, 0, nil, 1, nil, + nil, nil, nil, 1, 0, nil, nil, nil] + end + + let(:original_branches) do + { + [:if, 0, 13, 4, 17, 7] => { + [:then, 1, 14, 6, 14, 10] => 0, + [:else, 2, 16, 6, 16, 10] => 1 + } + } + end + + let(:result) do + SimpleCov::Result.new({ + source_fixture("json/sample.rb") => { + "lines" => original_lines, + "branches" => original_branches + } + }) + end + + before do + enable_branch_coverage + end + + it "works" do + subject.format(result, verbose: false) + expect(json_ouput).to eq(json_result("sample_with_branch")) + end + end + + context "with groups" do + let(:result) do + res = SimpleCov::Result.new({ + source_fixture("json/sample.rb") => {"lines" => [ + nil, 1, 1, 1, 1, nil, nil, 1, 1, nil, nil, + 1, 1, 0, nil, 1, nil, nil, nil, nil, 1, 0, nil, nil, nil + ]} + }) + + # right now SimpleCov works mostly on global state, hence setting the groups that way + # would be global state --> Mocking is better here + allow(res).to receive_messages(groups: {"My Group" => double("File List", covered_percent: 80.0)}) + res + end + + it "displays groups correctly in the JSON" do + subject.format(result, verbose: false) + expect(json_ouput).to eq(json_result("sample_groups")) + end + end + end + + def enable_branch_coverage + allow(SimpleCov).to receive(:branch_coverage?).and_return(true) + end + + def json_ouput + JSON.parse(File.read("tmp/coverage/coverage.json")) + end + + def json_result(filename) + file = File.read(source_fixture("json/#{filename}.json")) + file = use_current_working_directory(file) + JSON.parse(file) + end + + DEFAULT_WORKING_DIRECTORY = "STUB_WORKING_DIRECTORY" + def use_current_working_directory(file) + current_working_directory = File.expand_path("..", File.dirname(__FILE__)) + file.gsub!("/#{DEFAULT_WORKING_DIRECTORY}/", "#{current_working_directory}/") + + file + end +end diff --git a/test_projects/monorepo/Gemfile.lock b/test_projects/monorepo/Gemfile.lock index 92ea7b39..2162399d 100644 --- a/test_projects/monorepo/Gemfile.lock +++ b/test_projects/monorepo/Gemfile.lock @@ -4,7 +4,6 @@ PATH simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) - simplecov_json_formatter (~> 0.1) PATH remote: base @@ -36,7 +35,6 @@ GEM rspec-support (~> 3.9.0) rspec-support (3.9.3) simplecov-html (0.12.3) - simplecov_json_formatter (0.1.4) PLATFORMS ruby diff --git a/test_projects/parallel_tests/Gemfile.lock b/test_projects/parallel_tests/Gemfile.lock index a2961eea..7ccd4b81 100644 --- a/test_projects/parallel_tests/Gemfile.lock +++ b/test_projects/parallel_tests/Gemfile.lock @@ -4,7 +4,6 @@ PATH simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) - simplecov_json_formatter (~> 0.1) GEM remote: https://rubygems.org/ @@ -28,7 +27,6 @@ GEM rspec-support (~> 3.9.0) rspec-support (3.9.2) simplecov-html (0.12.3) - simplecov_json_formatter (0.1.4) PLATFORMS ruby