├── .rspec ├── .gitignore ├── bin └── run-image ├── lib ├── simplecov_json_formatter │ ├── version.rb │ ├── result_exporter.rb │ ├── result_hash_formatter.rb │ └── source_file_formatter.rb └── simplecov_json_formatter.rb ├── Dockerfile ├── test.rb ├── .rubocop.yml ├── Makefile ├── Gemfile ├── .circleci └── config.yml ├── spec ├── fixtures │ ├── sample.rb │ ├── sample.json │ ├── sample_groups.json │ └── sample_with_branch.json ├── spec_helper.rb └── simplecov_json_formatter_spec.rb ├── Rakefile ├── CHANGELOG.md ├── simplecov_json_formatter.gemspec ├── LICENSE ├── Gemfile.lock └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --warnings 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .byebug_history 2 | tmp 3 | 4 | -------------------------------------------------------------------------------- /bin/run-image: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exec docker run \ 4 | -it --rm \ 5 | --volume "$PWD:/gem" \ 6 | simplecov-json-formatter "$@" 7 | -------------------------------------------------------------------------------- /lib/simplecov_json_formatter/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SimpleCovJSONFormatter 4 | VERSION = '0.1.4' 5 | end 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.5 2 | 3 | WORKDIR /gem 4 | 5 | COPY . . 6 | 7 | RUN apt-get update -y && \ 8 | gem install bundler:2.1.0 && \ 9 | bundle install 10 | -------------------------------------------------------------------------------- /test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'coverage' 4 | Coverage.start(branches: true) 5 | load '/gem/spec/fixtures/sample.rb' 6 | foo = Foo.new 7 | foo.bar 8 | foo.foo(false) 9 | puts Coverage.result 10 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - 'spec/fixtures/**/*' 4 | TargetRubyVersion: 2.4 5 | 6 | Style/Documentation: 7 | Enabled: false 8 | 9 | Metrics/BlockLength: 10 | Exclude: 11 | - 'spec/**/*' 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: image test format sh 2 | 3 | image: 4 | docker image build -t simplecov-json-formatter . 5 | 6 | test: 7 | ./bin/run-image rake 8 | 9 | format: 10 | ./bin/run-image rubocop -a 11 | 12 | sh: 13 | ./bin/run-image sh 14 | 15 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | group :development do 6 | gem 'byebug' 7 | gem 'rake', '~> 12.0' 8 | gem 'rspec', '~> 3.2' 9 | gem 'rubocop' 10 | end 11 | 12 | group :test do 13 | gem 'simplecov', '~> 0.18' 14 | end 15 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.0 2 | 3 | jobs: 4 | build: 5 | machine: true 6 | working_directory: ~/simplecov-json-formatter 7 | steps: 8 | - checkout 9 | - run: 10 | name: Build 11 | command: make image 12 | - run: 13 | name: Test 14 | command: make test 15 | -------------------------------------------------------------------------------- /spec/fixtures/sample.rb: -------------------------------------------------------------------------------- 1 | # Foo class 2 | class Foo 3 | def initialize 4 | @foo = 'bar' 5 | @bar = 'foo' 6 | end 7 | 8 | def bar 9 | @foo 10 | end 11 | 12 | def foo(param) 13 | if param 14 | @bar 15 | else 16 | @foo 17 | end 18 | end 19 | 20 | #:nocov: 21 | def skipped 22 | @foo * 2 23 | end 24 | #:nocov: 25 | end 26 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/setup' 4 | require 'rake/testtask' 5 | require 'rspec/core/rake_task' 6 | 7 | RSpec::Core::RakeTask.new(:spec) 8 | 9 | begin 10 | require 'rubocop/rake_task' 11 | RuboCop::RakeTask.new 12 | rescue LoadError 13 | task :rubocop do 14 | warn 'Rubocop is disabled' 15 | end 16 | end 17 | 18 | task default: %i[rubocop spec] 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 0.1.4 (2022-02-12) 2 | ========== 3 | 4 | ## Enhancements 5 | * Add support for simplecov's groups feature -w [#2](https://github.com/codeclimate-community/simplecov_json_formatter/pull/2) @PragTob 6 | 7 | 0.1.3 (2021-05-02) 8 | ========== 9 | 10 | ## Bugfixes 11 | * avoid emitting warnings when ruby is run with -w [#1](https://github.com/codeclimate-community/simplecov_json_formatter/pull/1) @flavorjones 12 | 13 | 14 | -------------------------------------------------------------------------------- /lib/simplecov_json_formatter/result_exporter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SimpleCovJSONFormatter 4 | class ResultExporter 5 | FILENAME = 'coverage.json' 6 | 7 | def initialize(result_hash) 8 | @result = result_hash 9 | end 10 | 11 | def export 12 | File.open(export_path, 'w') do |file| 13 | file << json_result 14 | end 15 | end 16 | 17 | private 18 | 19 | def json_result 20 | JSON.pretty_generate(@result) 21 | end 22 | 23 | def export_path 24 | File.join(SimpleCov.coverage_path, FILENAME) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/fixtures/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "simplecov_version": "0.21.2" 4 | }, 5 | "coverage": { 6 | "/STUB_WORKING_DIRECTORY/spec/fixtures/sample.rb": { 7 | "lines": [ 8 | null, 9 | 1, 10 | 1, 11 | 1, 12 | 1, 13 | null, 14 | null, 15 | 1, 16 | 1, 17 | null, 18 | null, 19 | 1, 20 | 1, 21 | 0, 22 | null, 23 | 1, 24 | null, 25 | null, 26 | null, 27 | "ignored", 28 | "ignored", 29 | "ignored", 30 | "ignored", 31 | "ignored", 32 | null 33 | ] 34 | } 35 | }, 36 | "groups": {} 37 | } 38 | -------------------------------------------------------------------------------- /simplecov_json_formatter.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.push File.expand_path('lib', __dir__) 4 | require 'simplecov_json_formatter/version' 5 | 6 | Gem::Specification.new 'simplecov_json_formatter' do |s| 7 | s.version = SimpleCovJSONFormatter::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ['Federico Moya'] 10 | s.email = ['federicomoyamartin@gmail.com'] 11 | s.homepage = 'https://github.com/fede-moya/simplecov_json_formatter' 12 | s.summary = %(JSON formatter for SimpleCov) 13 | s.description = s.summary 14 | s.license = 'MIT' 15 | 16 | s.files = Dir['{lib}/**/*.*', '*.md'] 17 | s.require_paths = ['lib'] 18 | 19 | s.required_ruby_version = '>= 2.4.0' 20 | end 21 | -------------------------------------------------------------------------------- /spec/fixtures/sample_groups.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "simplecov_version": "0.21.2" 4 | }, 5 | "coverage": { 6 | "/STUB_WORKING_DIRECTORY/spec/fixtures/sample.rb": { 7 | "lines": [ 8 | null, 9 | 1, 10 | 1, 11 | 1, 12 | 1, 13 | null, 14 | null, 15 | 1, 16 | 1, 17 | null, 18 | null, 19 | 1, 20 | 1, 21 | 0, 22 | null, 23 | 1, 24 | null, 25 | null, 26 | null, 27 | "ignored", 28 | "ignored", 29 | "ignored", 30 | "ignored", 31 | "ignored", 32 | null 33 | ] 34 | } 35 | }, 36 | "groups": { 37 | "My Group": { 38 | "lines": { 39 | "covered_percent": 80.0 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspec' 4 | require 'simplecov' 5 | require 'simplecov_json_formatter' 6 | 7 | SimpleCov.coverage_dir('tmp/coverage') 8 | 9 | def enable_branch_coverage 10 | allow(SimpleCov).to receive(:branch_coverage?).and_return(true) 11 | end 12 | 13 | def source_fixture(filename) 14 | File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', filename)) 15 | end 16 | 17 | def json_ouput 18 | JSON.parse(File.read('tmp/coverage/coverage.json')) 19 | end 20 | 21 | def json_result(filename) 22 | file = File.read(source_fixture("#{filename}.json")) 23 | file = use_current_working_directory(file) 24 | JSON.parse(file) 25 | end 26 | 27 | DEFAULT_WORKING_DIRECTORY = 'STUB_WORKING_DIRECTORY' 28 | def use_current_working_directory(file) 29 | current_working_directory = File.expand_path('..', File.dirname(__FILE__)) 30 | file.gsub!("/#{DEFAULT_WORKING_DIRECTORY}/", "#{current_working_directory}/") 31 | 32 | file 33 | end 34 | -------------------------------------------------------------------------------- /spec/fixtures/sample_with_branch.json: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "simplecov_version": "0.21.2" 4 | }, 5 | "coverage": { 6 | "/STUB_WORKING_DIRECTORY/spec/fixtures/sample.rb": { 7 | "lines": [ 8 | null, 9 | 1, 10 | 1, 11 | 1, 12 | 1, 13 | null, 14 | null, 15 | 1, 16 | 1, 17 | null, 18 | null, 19 | 1, 20 | 1, 21 | 0, 22 | null, 23 | 1, 24 | null, 25 | null, 26 | null, 27 | "ignored", 28 | "ignored", 29 | "ignored", 30 | "ignored", 31 | "ignored", 32 | null 33 | ], 34 | "branches": [ 35 | { 36 | "type": "then", 37 | "start_line": 14, 38 | "end_line": 14, 39 | "coverage": 0 40 | }, 41 | { 42 | "type": "else", 43 | "start_line": 16, 44 | "end_line": 16, 45 | "coverage": 1 46 | } 47 | ] 48 | } 49 | }, 50 | "groups": {} 51 | } 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Code Climate 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /lib/simplecov_json_formatter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'simplecov_json_formatter/result_hash_formatter' 4 | require 'simplecov_json_formatter/result_exporter' 5 | require 'json' 6 | 7 | module SimpleCov 8 | module Formatter 9 | class JSONFormatter 10 | def format(result) 11 | result_hash = format_result(result) 12 | 13 | export_formatted_result(result_hash) 14 | 15 | puts output_message(result) 16 | end 17 | 18 | private 19 | 20 | def format_result(result) 21 | result_hash_formater = SimpleCovJSONFormatter::ResultHashFormatter.new(result) 22 | result_hash_formater.format 23 | end 24 | 25 | def export_formatted_result(result_hash) 26 | result_exporter = SimpleCovJSONFormatter::ResultExporter.new(result_hash) 27 | result_exporter.export 28 | end 29 | 30 | def output_message(result) 31 | "JSON Coverage report generated for #{result.command_name} to #{SimpleCov.coverage_path}. " \ 32 | "#{result.covered_lines} / #{result.total_lines} LOC (#{result.covered_percent.round(2)}%) covered." 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/simplecov_json_formatter/result_hash_formatter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'simplecov_json_formatter/source_file_formatter' 4 | 5 | module SimpleCovJSONFormatter 6 | class ResultHashFormatter 7 | def initialize(result) 8 | @result = result 9 | end 10 | 11 | def format 12 | format_files 13 | format_groups 14 | 15 | formatted_result 16 | end 17 | 18 | private 19 | 20 | def format_files 21 | @result.files.each do |source_file| 22 | formatted_result[:coverage][source_file.filename] = 23 | format_source_file(source_file) 24 | end 25 | end 26 | 27 | def format_groups 28 | @result.groups.each do |name, file_list| 29 | formatted_result[:groups][name] = { 30 | lines: { 31 | covered_percent: file_list.covered_percent 32 | } 33 | } 34 | end 35 | end 36 | 37 | def formatted_result 38 | @formatted_result ||= { 39 | meta: { 40 | simplecov_version: SimpleCov::VERSION 41 | }, 42 | coverage: {}, 43 | groups: {} 44 | } 45 | end 46 | 47 | def format_source_file(source_file) 48 | source_file_formatter = SourceFileFormatter.new(source_file) 49 | source_file_formatter.format 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/simplecov_json_formatter/source_file_formatter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SimpleCovJSONFormatter 4 | class SourceFileFormatter 5 | def initialize(source_file) 6 | @source_file = source_file 7 | @line_coverage = nil 8 | end 9 | 10 | def format 11 | if SimpleCov.branch_coverage? 12 | line_coverage.merge(branch_coverage) 13 | else 14 | line_coverage 15 | end 16 | end 17 | 18 | private 19 | 20 | def line_coverage 21 | @line_coverage ||= { 22 | lines: lines 23 | } 24 | end 25 | 26 | def branch_coverage 27 | { 28 | branches: branches 29 | } 30 | end 31 | 32 | def lines 33 | lines = [] 34 | @source_file.lines.each do |line| 35 | lines << parse_line(line) 36 | end 37 | 38 | lines 39 | end 40 | 41 | def branches 42 | branches = [] 43 | @source_file.branches.each do |branch| 44 | branches << parse_branch(branch) 45 | end 46 | 47 | branches 48 | end 49 | 50 | def parse_line(line) 51 | return line.coverage unless line.skipped? 52 | 53 | 'ignored' 54 | end 55 | 56 | def parse_branch(branch) 57 | { 58 | type: branch.type, 59 | start_line: branch.start_line, 60 | end_line: branch.end_line, 61 | coverage: parse_line(branch) 62 | } 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | ast (2.4.1) 5 | byebug (11.1.3) 6 | diff-lcs (1.4.4) 7 | docile (1.4.0) 8 | parallel (1.19.2) 9 | parser (2.7.1.4) 10 | ast (~> 2.4.1) 11 | rainbow (3.0.0) 12 | rake (12.3.3) 13 | regexp_parser (1.7.1) 14 | rexml (3.2.4) 15 | rspec (3.9.0) 16 | rspec-core (~> 3.9.0) 17 | rspec-expectations (~> 3.9.0) 18 | rspec-mocks (~> 3.9.0) 19 | rspec-core (3.9.2) 20 | rspec-support (~> 3.9.3) 21 | rspec-expectations (3.9.2) 22 | diff-lcs (>= 1.2.0, < 2.0) 23 | rspec-support (~> 3.9.0) 24 | rspec-mocks (3.9.1) 25 | diff-lcs (>= 1.2.0, < 2.0) 26 | rspec-support (~> 3.9.0) 27 | rspec-support (3.9.3) 28 | rubocop (0.89.1) 29 | parallel (~> 1.10) 30 | parser (>= 2.7.1.1) 31 | rainbow (>= 2.2.2, < 4.0) 32 | regexp_parser (>= 1.7) 33 | rexml 34 | rubocop-ast (>= 0.3.0, < 1.0) 35 | ruby-progressbar (~> 1.7) 36 | unicode-display_width (>= 1.4.0, < 2.0) 37 | rubocop-ast (0.3.0) 38 | parser (>= 2.7.1.4) 39 | ruby-progressbar (1.10.1) 40 | simplecov (0.21.2) 41 | docile (~> 1.1) 42 | simplecov-html (~> 0.11) 43 | simplecov_json_formatter (~> 0.1) 44 | simplecov-html (0.12.3) 45 | simplecov_json_formatter (0.1.3) 46 | unicode-display_width (1.7.0) 47 | 48 | PLATFORMS 49 | ruby 50 | 51 | DEPENDENCIES 52 | byebug 53 | rake (~> 12.0) 54 | rspec (~> 3.2) 55 | rubocop 56 | simplecov (~> 0.18) 57 | 58 | BUNDLED WITH 59 | 2.3.4 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSON formatter for SimpleCov 2 | 3 | ***Note: To learn more about SimpleCov, check out the main repo at [SimpleCov](https://github.com/simplecov-ruby/simplecov)*** 4 | 5 | Generates a formatted JSON report of your [SimpleCov](https://github.com/simplecov-ruby/simplecov) ruby code coverage results on ruby 2.4+. Originally intended to add `simplecov`'s results reading capacity to CI tools. 6 | 7 | ## Overview 8 | 9 | You can expect for this gem to produce a `coverage.json` file, located at the `coverage` folder. 10 | 11 | Depending on your `SimpleCoV`'s settings you will experiment different outcomes. Particularly depending on which type of coverage are you running `SimpleCov` with: 12 | 13 | - If you configure `SimpleCov` to run with `branch` coverage you should expect an output formatted like [sample_with_branch.json](https://github.com/fede-moya/simplecov_json_formatter/blob/master/spec/fixtures/sample_with_branch.json) 14 | - Otherwise you should expect an output formatted like [sample.json](https://github.com/fede-moya/simplecov_json_formatter/blob/master/spec/fixtures/sample.json) 15 | 16 | ## Development 17 | 18 | We encourage you to use docker for common operations like running tests, or debugging your code. Running `make sh` will start a new container instance based on the `Dockerfile` provided at root, finally a shell prompt will be displayed on your terminal. Also, syncronization with your local files will be already set. 19 | 20 | ### Tests 21 | `make test` will trigger the excution of both running tests and running rubocop as linter, by simply running `rake`, this actions will be run inside a new container but using your local files. 22 | 23 | ### Format 24 | 25 | `make format` will run `rubocop -a` which stands for _autocorrect_ and format your code according to the `.rubocop.yml` config file. 26 | 27 | ## Copyright 28 | 29 | See [License](https://github.com/codeclimate-community/simplecov_json_formatter/blob/master/LICENSE) 30 | -------------------------------------------------------------------------------- /spec/simplecov_json_formatter_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'byebug' 5 | 6 | describe SimpleCov::Formatter::JSONFormatter do 7 | let(:result) do 8 | SimpleCov::Result.new({ 9 | source_fixture('sample.rb') => { 'lines' => [ 10 | nil, 1, 1, 1, 1, nil, nil, 1, 1, nil, nil, 11 | 1, 1, 0, nil, 1, nil, nil, nil, nil, 1, 0, nil, nil, nil 12 | ] } 13 | }) 14 | end 15 | 16 | describe 'format' do 17 | context 'with line coverage' do 18 | it 'works' do 19 | subject.format(result) 20 | expect(json_ouput).to eq(json_result('sample')) 21 | end 22 | end 23 | 24 | context 'with branch coverage' do 25 | let(:original_lines) do 26 | [nil, 1, 1, 1, 1, nil, nil, 1, 1, 27 | nil, nil, 1, 1, 0, nil, 1, nil, 28 | nil, nil, nil, 1, 0, nil, nil, nil] 29 | end 30 | 31 | let(:original_branches) do 32 | { 33 | [:if, 0, 13, 4, 17, 7] => { 34 | [:then, 1, 14, 6, 14, 10] => 0, 35 | [:else, 2, 16, 6, 16, 10] => 1 36 | } 37 | } 38 | end 39 | 40 | let(:result) do 41 | SimpleCov::Result.new({ 42 | source_fixture('sample.rb') => { 43 | 'lines' => original_lines, 44 | 'branches' => original_branches 45 | } 46 | }) 47 | end 48 | 49 | before do 50 | enable_branch_coverage 51 | end 52 | 53 | it 'works' do 54 | subject.format(result) 55 | expect(json_ouput).to eq(json_result('sample_with_branch')) 56 | end 57 | end 58 | 59 | context 'with groups' do 60 | let(:result) do 61 | res = SimpleCov::Result.new({ 62 | source_fixture('sample.rb') => { 'lines' => [ 63 | nil, 1, 1, 1, 1, nil, nil, 1, 1, nil, nil, 64 | 1, 1, 0, nil, 1, nil, nil, nil, nil, 1, 0, nil, nil, nil 65 | ] } 66 | }) 67 | 68 | # right now SimpleCov works mostly on global state, hence setting the groups that way 69 | # would be global state --> Mocking is better here 70 | allow(res).to receive_messages(groups: { 'My Group' => double('File List', covered_percent: 80.0) }) 71 | res 72 | end 73 | 74 | it 'displays groups correctly in the JSON' do 75 | subject.format(result) 76 | expect(json_ouput).to eq(json_result('sample_groups')) 77 | end 78 | end 79 | end 80 | end 81 | --------------------------------------------------------------------------------