From 9b4a65b6fa18d4a1c3fcb5f0a114821274d9b875 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Fri, 21 Aug 2015 03:59:52 -0400 Subject: [PATCH] Add test coverage; account for no artifacts on CI --- .simplecov | 77 ++++++++++++++++++++++++++++++++++++++++ Gemfile | 16 +++++---- Rakefile | 7 +++- test/capture_warnings.rb | 41 +++++++++++++++++---- test/test_helper.rb | 9 ++++- 5 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 .simplecov diff --git a/.simplecov b/.simplecov new file mode 100644 index 000000000..bd70afa99 --- /dev/null +++ b/.simplecov @@ -0,0 +1,77 @@ +# https://github.com/colszowka/simplecov#using-simplecov-for-centralized-config +# see https://github.com/colszowka/simplecov/blob/master/lib/simplecov/defaults.rb +# vim: set ft=ruby +@minimum_coverage = ENV.fetch('COVERAGE_MINIMUM') { 98.3 }.to_f.round(2) +# rubocop:disable Style/DoubleNegation +ENV['FULL_BUILD'] ||= ENV['CI'] +@running_ci = !!(ENV['FULL_BUILD'] =~ /\Atrue\z/i) +@generate_report = @running_ci || !!(ENV['COVERAGE'] =~ /\Atrue\z/i) +@output = STDERR +# rubocop:enable Style/DoubleNegation + +SimpleCov.pid = $$ # In case there's any forking + +SimpleCov.profiles.define 'app' do + coverage_dir 'coverage' + load_profile 'test_frameworks' + + add_group 'Libraries', 'lib' + + add_group 'Long files' do |src_file| + src_file.lines.count > 100 + end + class MaxLinesFilter < SimpleCov::Filter + def matches?(source_file) + source_file.lines.count < filter_argument + end + end + add_group 'Short files', MaxLinesFilter.new(5) + + # Exclude these paths from analysis + add_filter '/config/' + add_filter '/db/' + add_filter 'tasks' +end + +## START TRACKING COVERAGE +require 'coverage' +Coverage.start + +# rubocop:disable Style/MultilineBlockChain +AppCoverage = Class.new do + def initialize(&block) + @block = block + end + + def start + @block.call + end +end.new do + SimpleCov.start 'app' + if @generate_report + if @running_ci + @output.puts '[COVERAGE] Running with SimpleCov Simple Formatter' + formatters = [SimpleCov::Formatter::SimpleFormatter] + else + @output.puts '[COVERAGE] Running with SimpleCov HTML Formatter' + formatters = [SimpleCov::Formatter::HTMLFormatter] + end + else + formatters = [] + end + SimpleCov.formatters = formatters +end +# rubocop:enable Style/MultilineBlockChain +SimpleCov.at_exit do + @output.puts "*" * 50 + @output.puts SimpleCov.result.format! + percent = Float(SimpleCov.result.covered_percent) + if percent < @minimum_coverage + @output.puts "Spec coverage was not high enough: "\ + "#{percent.round(2)} is < #{@minimum_coverage}%\n" + exit 1 if @generate_report + else + @output.puts "Nice job! Spec coverage (#{percent.round(2)}) "\ + "is still at or above #{@minimum_coverage}%\n" + end +end diff --git a/Gemfile b/Gemfile index baf12948d..c872d9f72 100644 --- a/Gemfile +++ b/Gemfile @@ -3,15 +3,19 @@ source 'https://rubygems.org' # Specify your gem's dependencies in active_model_serializers.gemspec gemspec -gem "minitest" +gem 'minitest' -version = ENV["RAILS_VERSION"] || "4.2" +version = ENV['RAILS_VERSION'] || '4.2' -if version == "master" - gem "rails", github: "rails/rails" +if version == 'master' + gem 'rails', github: 'rails/rails' # ugh https://github.com/rails/rails/issues/16063#issuecomment-48090125 - gem "arel", github: "rails/arel" + gem 'arel', github: 'rails/arel' else - gem "rails", "~> #{version}.0" + gem 'rails', "~> #{version}.0" +end + +group :test, :development do + gem 'simplecov', '~> 0.10', require: false end diff --git a/Rakefile b/Rakefile index 8a1f1e9e8..cecbb6091 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,9 @@ -require "bundler/gem_tasks" +begin + require 'simplecov' +rescue LoadError +end + +require 'bundler/gem_tasks' require 'rake/testtask' diff --git a/test/capture_warnings.rb b/test/capture_warnings.rb index f7ea759ba..a3e0924d4 100644 --- a/test/capture_warnings.rb +++ b/test/capture_warnings.rb @@ -13,9 +13,8 @@ def initialize(fail_on_warnings = true) end def before_tests - $stderr.reopen(stderr_file.path) $VERBOSE = true - at_exit { $stderr.reopen(STDERR) } + @restore_output = capture_output(stderr_file) end def after_tests @@ -23,14 +22,15 @@ def after_tests lines = stderr_file.read.split("\n").uniq stderr_file.close! - $stderr.reopen(STDERR) + # $stderr.reopen(STDERR) + @restore_output.call app_warnings, other_warnings = lines.partition { |line| line.include?(app_root) && !line.include?(bundle_dir) } if app_warnings.any? - puts <<-WARNINGS + STDERR.puts <<-WARNINGS #{'-' * 30} app warnings: #{'-' * 30} #{app_warnings.join("\n")} @@ -41,9 +41,9 @@ def after_tests if other_warnings.any? File.write(File.join(output_dir, "warnings.txt"), other_warnings.join("\n") << "\n") - puts - puts "Non-app warnings written to tmp/warnings.txt" - puts + STDERR.puts + STDERR.puts "Non-app warnings written to tmp/warnings.txt" + STDERR.puts end # fail the build... @@ -54,4 +54,31 @@ def after_tests private attr_reader :stderr_file, :app_root, :output_dir, :bundle_dir, :fail_on_warnings + + # From episode 029 of Ruby Tapas by Avdi + # https://rubytapas.dpdcart.com/subscriber/post?id=88 + def capture_output(output, stream = STDERR) + old_stdout = stream.clone + pipe_r, pipe_w = IO.pipe + pipe_r.sync = true + reader = Thread.new do + begin + loop do + output << pipe_r.readpartial(1024) + end + rescue EOFError + end + end + stream.reopen(pipe_w) + ensure + restore_output = proc do + stream.reopen(old_stdout) + pipe_w.close if pipe_w.respond_to?(:closed?) && !pipe_w.closed? + reader.join if reader.alive? + pipe_r.close if pipe_r.respond_to?(:closed?) && !pipe_r.closed? + end + at_exit(&restore_output) + return restore_output + end + end diff --git a/test/test_helper.rb b/test/test_helper.rb index 68d844e69..b470870f0 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,5 +1,12 @@ require 'bundler/setup' +begin + require 'simplecov' + AppCoverage.start +rescue LoadError + STDERR.puts 'Running without SimpleCov' +end + require 'timecop' require 'rails' require 'action_controller' @@ -14,7 +21,7 @@ require "capture_warnings" @capture_warnings = CaptureWarnings.new(fail_build = false) @capture_warnings.before_tests -at_exit do +Minitest.after_run do @capture_warnings.after_tests end require 'active_model_serializers'