Skip to content

Commit

Permalink
Merge pull request #183 from danmayer/feature/gem_tracking
Browse files Browse the repository at this point in the history
WIP: Feature/gem tracking
  • Loading branch information
danmayer committed Feb 10, 2019
2 parents 32c8188 + 7cd7ffe commit 2f0785e
Show file tree
Hide file tree
Showing 30 changed files with 567 additions and 83 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ source 'https://rubygems.org'
# Specify your gem's dependencies in coverband.gemspec
gemspec
gem 'rails', '~>5'
# this is used for testing gem tracking
gem 'rainbow', require: false
2 changes: 2 additions & 0 deletions Gemfile.rails4
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ source 'https://rubygems.org'
# Specify your gem's dependencies in coverband.gemspec
gemspec
gem 'rails', '~>4'
# this is used for testing gem tracking
gem 'rainbow', require: false
13 changes: 7 additions & 6 deletions changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ Will be the fully modern release that drops maintenance legacy support in favor
- more details in this issue: https://github.com/danmayer/coverband/issues/118
- Make good video on setup, install, usage
- See if we can add support for views / templates
- using this technique https://github.com/ioquatix/covered
- using this technique https://github.com/ioquatix/covered
- Better default grouping (could use groups features for gems for rails controllers, models, lib, etc)

### Coverband_jam_session

Expand Down Expand Up @@ -66,16 +67,16 @@ Feature Ideas:

- pilot release of Gems tracking
- todos
- escape on gem file should go back to gems tab
- support multiple gem paths with a rollout
- direct url support for gems #gem-name detection like it works for tabs
- full test coverage on gem code paths
- fix any perf impacts on non-gem flows
- support multiple gem paths (various version managers setup multiple gem paths)
- speed up page load by allowing multiple pages
- web settings and debug views
- todo improve these views

### Coverband 4.1.0.alpha

- default disabled web clear, add config option to allow it
- out of the box support for resque
- readme improvements

# Released

Expand Down
2 changes: 2 additions & 0 deletions lib/coverband.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
require 'coverband/utils/html_formatter'
require 'coverband/utils/result'
require 'coverband/utils/file_list'
require 'coverband/utils/gem_list'
require 'coverband/utils/source_file'
require 'coverband/utils/file_groups'
require 'coverband/utils/lines_classifier'
require 'coverband/utils/railtie' if defined? ::Rails::Railtie
require 'coverband/collectors/coverage'
Expand Down
6 changes: 0 additions & 6 deletions lib/coverband/adapters/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,6 @@ def merge_expanded_data(new_expanded, old_expanded)
def array_add(latest, original)
latest.map.with_index { |v, i| (v && original[i]) ? v + original[i] : nil }
end

def simple_report(report)
report.each_with_object({}) do |(key, extended_data), simple|
simple[key] = extended_data['data']
end
end
end
end
end
11 changes: 10 additions & 1 deletion lib/coverband/collectors/coverage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def reset_instance
@logger = Coverband.configuration.logger
@current_thread = Thread.current
@test_env = Coverband.configuration.test_env
@track_gems = Coverband.configuration.track_gems
@@previous_results = nil
Thread.current[:coverband_instance] = nil
self
Expand All @@ -48,10 +49,18 @@ def report_coverage(force_report = false)

protected

###
# Normally I would break this out into additional methods
# and improve the readability but this is in a tight loop
# on the critical performance path, and any refactoring I come up with
# would slow down the performance.
###
def track_file?(file)
@ignore_patterns.none? do |pattern|
file.include?(pattern)
end && file.start_with?(@project_directory)
end && (file.start_with?(@project_directory) ||
(@track_gems &&
Coverband.configuration.gem_paths.any? { |path| file.start_with?(path) }))
end

private
Expand Down
57 changes: 55 additions & 2 deletions lib/coverband/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ class Configuration
:redis_namespace, :redis_ttl,
:safe_reload_files, :background_reporting_enabled,
:background_reporting_sleep_seconds, :test_env,
:web_enable_clear
:web_enable_clear, :gem_details, :web_debug

attr_writer :logger, :s3_region, :s3_bucket, :s3_access_key_id, :s3_secret_access_key
attr_reader :track_gems

def initialize
reset
Expand All @@ -19,7 +20,7 @@ def initialize
def reset
@root = Dir.pwd
@root_paths = []
@ignore = %w(vendor .erb$ .slim$)
@ignore = %w[vendor .erb$ .slim$]
@additional_files = []
@reporting_frequency = 0.0
@verbose = false
Expand All @@ -30,6 +31,10 @@ def reset
@background_reporting_sleep_seconds = 30
@test_env = nil
@web_enable_clear = false
@track_gems = false
@gem_details = false
@groups = {}
@web_debug = false

# TODO: should we push these to adapter configs
@s3_region = nil
Expand Down Expand Up @@ -76,6 +81,54 @@ def store=(store)
end
end

def track_gems=(value)
@track_gems = value
return unless @track_gems
# by default we ignore vendor where many deployments put gems
# we will remove this default if track_gems is set
@ignore.delete('vendor')
# while we want to allow vendored gems we don't want to track vendored ruby STDLIB
@ignore << 'vendor/ruby-*'
add_group('App', root)
# TODO: rework support for multiple gem paths
# currently this supports GEM_HOME (which should be first path)
# but various gem managers setup multiple gem paths
# gem_paths.each_with_index do |path, index|
# add_group("gems_#{index}", path)
# end
add_group('Gems', gem_paths.first)
end

#
# Returns the configured groups. Add groups using SimpleCov.add_group
#
def groups
@groups ||= {}
end

#
# Define a group for files. Works similar to add_filter, only that the first
# argument is the desired group name and files PASSING the filter end up in the group
# (while filters exclude when the filter is applicable).
#
def add_group(group_name, filter_argument = nil)
groups[group_name] = filter_argument
end

def gem_paths
# notes ignore any paths that aren't on this system, resolves
# bug related to multiple ruby version managers / bad dot files
Gem::PathSupport.new(ENV).path.select { |path| File.exist?(path) }
end

SKIPPED_SETTINGS = %w[@s3_secret_access_key @store]
def to_h
instance_variables
.each_with_object('gem_paths': gem_paths) do |var, hash|
hash[var.to_s.delete('@')] = instance_variable_get(var) unless SKIPPED_SETTINGS.include?(var.to_s)
end
end

private

def redis_url
Expand Down
18 changes: 12 additions & 6 deletions lib/coverband/reporters/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def report(store, _options = {})

def root_paths
roots = Coverband.configuration.root_paths
roots += Coverband.configuration.gem_paths if Coverband.configuration.track_gems
roots << "#{current_root}/"
roots
end
Expand Down Expand Up @@ -81,15 +82,20 @@ def merge_arrays(first, second)
# example: '/var/local/company/company.d/[0-9]*/'
###
def filename_from_key(key, roots)
filename = key
relative_filename = key
local_filename = relative_filename
roots.each do |root|
filename = filename.gsub(/^#{root}/, './')
relative_filename = relative_filename.gsub(/^#{root}/, './')
end
# The filename for Coverage report is expected to be a full LOCAL path.
# the filename for our reports is expected to be a full path.
# roots.last should be roots << current_root}/
# a fully expanded path of the file on the current runtime system
filename = filename.gsub('./', roots.last)
filename
# a fully expanded path of config.root
# filename = filename.gsub('./', roots.last)
# above only works for app files
# we need to rethink some of this logic
# gems aren't at project root and can have multiple locations
local_root = roots.find { |root| File.exist?(relative_filename.gsub('./', root)) }
local_root ? relative_filename.gsub('./', local_root) : local_filename
end

###
Expand Down
12 changes: 12 additions & 0 deletions lib/coverband/reporters/web.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ def call(env)
case request.path_info
when /.*\.(css|js|gif|png)/
@static.call(env)
when %r{\/settings}
[200, { 'Content-Type' => 'text/html' }, [settings]]
when %r{\/debug_data}
[200, { 'Content-Type' => 'text/json' }, [debug_data]]
when %r{\/$}
[200, { 'Content-Type' => 'text/html' }, [index]]
else
Expand All @@ -53,6 +57,14 @@ def index
open_report: false)
end

def settings
Coverband::Utils::HTMLFormatter.new(nil, base_path: base_path).format_settings!
end

def debug_data
Coverband.configuration.store.coverage.to_json
end

def collect_coverage
Coverband::Collectors::Coverage.instance.report_coverage(true)
notice = 'coverband coverage collected'
Expand Down
53 changes: 53 additions & 0 deletions lib/coverband/utils/file_groups.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# frozen_string_literal: true

#
# Applies the configured groups to the given array of Coverband::SourceFile items
#
module Coverband
module Utils
class FileGroups
def initialize(files)
@grouped = {}
@files = files
filter_to_groups
end

def grouped_results
@grouped
end

private

def filter_to_groups
grouped_files = []
Coverband.configuration.groups.each do |name, filter|
if name == 'Gems'
gem_lists = gem_files(name, filter)
grouped_files.concat(gem_lists.flatten) if gem_lists.flatten.any?
else
app_files(name, filter)
grouped_files += @grouped[name]
end
end
if !Coverband.configuration.groups.empty? && !(other_files = @files.reject do |source_file|
grouped_files.include?(source_file)
end).empty?
@grouped['Ungrouped'] = Coverband::Utils::FileList.new(other_files)
end
end

def gem_files(name, filter)
grouped_gems = @files.select { |source_file| source_file.filename =~ /#{filter}/ }.group_by(&:gem_name)
gem_lists = grouped_gems.values.map { |gem_files| Coverband::Utils::FileList.new(gem_files) }
@grouped[name] = Coverband::Utils::GemList.new(gem_lists) if gem_lists.flatten.any?
gem_lists
end

def app_files(name, filter)
@grouped[name] = Coverband::Utils::FileList.new(@files.select do |source_file|
source_file.filename =~ /#{filter}/ && source_file.filename !~ /#{Coverband.configuration.gem_paths.first}/
end)
end
end
end
end
31 changes: 31 additions & 0 deletions lib/coverband/utils/gem_list.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

####
# An array of FileLists instances with helpers to roll up the stats
# methods for calculating coverage across them etc.
####
module Coverband
module Utils
class GemList < FileList
# Returns the count of lines that have coverage
def covered_lines
to_a.map(&:covered_lines).inject(:+)
end

# Returns the count of lines that have been missed
def missed_lines
to_a.map(&:missed_lines).inject(:+)
end

# Returns the count of lines that are not relevant for coverage
def never_lines
to_a.map(&:never_lines).inject(:+)
end

# Returns the count of skipped lines
def skipped_lines
to_a.map(&:skipped_lines).inject(:+)
end
end
end
end
Loading

0 comments on commit 2f0785e

Please sign in to comment.