From d1c2fed9176c1c0f2a0e6ed98310c3d40d2cbc24 Mon Sep 17 00:00:00 2001 From: Kim Burgestrand Date: Thu, 10 Oct 2024 14:20:12 +0200 Subject: [PATCH] Make code/test coverage a thing we care about It's not perfect, but it is something. Full line coverage, full branch coverage, checked for every PR. --- .github/workflows/main.yml | 26 ++++++++ .rubocop.yml | 3 + lib/pundit/rspec.rb | 17 ++++- spec/simple_cov_check_action_formatter.rb | 78 +++++++++++++++++++++++ spec/spec_helper.rb | 11 ++++ 5 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 spec/simple_cov_check_action_formatter.rb diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fd8c8708..c8bc1998 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -77,12 +77,38 @@ jobs: run: bundle exec rspec env: COVERAGE: 1 + - name: Upload coverage results + uses: actions/upload-artifact@v4 + with: + include-hidden-files: true + name: coverage-results + path: coverage + retention-days: 1 - name: Upload code coverage to Code Climate run: | ./cc-test-reporter after-build \ --coverage-input-type simplecov \ ./coverage/.resultset.json + coverage-check: + permissions: + checks: write + needs: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Download coverage results + uses: actions/download-artifact@v4 + with: + name: coverage-results + path: coverage + - uses: joshmfrankel/simplecov-check-action@be89e11889202cc59efb14aab2a7091622fa9aad + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + minimum_suite_coverage: 100 + minimum_file_coverage: 100 + coverage_json_path: coverage/simplecov-check-action.json + rubocop: runs-on: ubuntu-latest steps: diff --git a/.rubocop.yml b/.rubocop.yml index 827c1d55..b4f65c11 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -39,6 +39,9 @@ Layout/CaseIndentation: Layout/FirstHashElementIndentation: EnforcedStyle: consistent +Layout/FirstArrayElementIndentation: + EnforcedStyle: consistent + Layout/EndAlignment: EnforcedStyleAlignWith: variable diff --git a/lib/pundit/rspec.rb b/lib/pundit/rspec.rb index 0aa54fa2..b54313f5 100644 --- a/lib/pundit/rspec.rb +++ b/lib/pundit/rspec.rb @@ -67,14 +67,29 @@ def description(user, record) failure_message(&failure_message_proc) failure_message_when_negated(&failure_message_when_negated_proc) else + # :nocov: + # Compatibility with RSpec < 3.0, released 2014-06-01. match_for_should(&match_proc) match_for_should_not(&match_when_negated_proc) failure_message_for_should(&failure_message_proc) failure_message_for_should_not(&failure_message_when_negated_proc) + # :nocov: + end + + if ::RSpec.respond_to?(:current_example) + def current_example + ::RSpec.current_example + end + else + # :nocov: + # Compatibility with RSpec < 3.0, released 2014-06-01. + def current_example + example + end + # :nocov: end def permissions - current_example = ::RSpec.respond_to?(:current_example) ? ::RSpec.current_example : example current_example.metadata.fetch(:permissions) do raise KeyError, <<~ERROR.strip No permissions in example metadata, did you forget to wrap with `permissions :show?, ...`? diff --git a/spec/simple_cov_check_action_formatter.rb b/spec/simple_cov_check_action_formatter.rb new file mode 100644 index 00000000..28b2b237 --- /dev/null +++ b/spec/simple_cov_check_action_formatter.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require "simplecov" +require "json" + +class SimpleCovCheckActionFormatter + SourceFile = Data.define(:source_file) do + def covered_strength = source_file.covered_strength + + def to_json(*) + { + filename: source_file.filename, + covered_percent: source_file.covered_percent, + coverage: source_file.coverage_data, + covered_strength: covered_strength.nan? ? 0.0 : covered_strength, + covered_lines: source_file.covered_lines.count, + lines_of_code: source_file.lines_of_code + }.to_json + end + end + + Result = Data.define(:result) do + def included?(source_file) = result.filenames.include?(source_file.filename) + + def files + result.files.filter_map do |source_file| + next unless result.filenames.include? source_file.filename + + SourceFile.new(source_file) + end + end + + def to_json(*) # rubocop:disable Metrics/AbcSize + { + timestamp: result.created_at.to_i, + command_name: result.command_name, + files: files, + metrics: { + covered_percent: result.covered_percent, + covered_strength: result.covered_strength.nan? ? 0.0 : result.covered_strength, + covered_lines: result.covered_lines, + total_lines: result.total_lines + } + }.to_json + end + end + + FormatterWithOptions = Data.define(:formatter) do + def new = formatter + end + + class << self + def with_options(...) + FormatterWithOptions.new(new(...)) + end + end + + def initialize(output_filename: "coverage.json", output_directory: SimpleCov.coverage_path) + @output_filename = output_filename + @output_directory = output_directory + end + + attr_reader :output_filename, :output_directory + + def output_filepath = File.join(output_directory, output_filename) + + def format(result_data) + result = Result.new(result_data) + json = result.to_json + File.write(output_filepath, json) + puts output_message(result_data) + json + end + + def output_message(result) + "Coverage report generated for #{result.command_name} to #{output_filepath}. #{result.covered_lines} / #{result.total_lines} LOC (#{result.covered_percent.round(2)}%) covered." # rubocop:disable Layout/LineLength + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8f3b5c31..4b932571 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,8 +2,19 @@ if ENV["COVERAGE"] require "simplecov" + require "simplecov_json_formatter" + require_relative "simple_cov_check_action_formatter" + SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new([ + SimpleCov::Formatter::HTMLFormatter, + SimpleCov::Formatter::JSONFormatter, + SimpleCovCheckActionFormatter.with_options( + output_filename: "simplecov-check-action.json" + ) + ]) SimpleCov.start do add_filter "/spec/" + enable_coverage :branch + primary_coverage :branch end end