undercover 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 83b2a35bc1bcadd4892ed21804bf9fd7bd2b3a7a
4
+ data.tar.gz: 91ed40ea4c090ce57d05532ed4ab986fe12fd873
5
+ SHA512:
6
+ metadata.gz: adeff5d8fe3e1a31b17427f0f53db3d62afaa50802aa52ac15b02a9adfcce82170147b6e327a6fcf59fbf4a9aa64828f7ad830b8aac2aa0ffd388af83643c4bb
7
+ data.tar.gz: ef8f9f142bdcf0369a978fd765cbede85d39807cf79f3c5fb7ed89115c88725d4e8e1e3391cf01477e507391884aea7ce54dde04dd2520167fdd4c042a5b41e7
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,14 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.4.4
3
+
4
+ Style/RegexpLiteral:
5
+ EnforcedStyle: slashes
6
+ AllowInnerSlashes: true
7
+
8
+ Style/Documentation:
9
+ Enabled: false
10
+
11
+ Metrics/BlockLength:
12
+ Enabled: true
13
+ Exclude:
14
+ - spec/**/*
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.5.1
4
+ - 2.4.4
5
+ - 2.3.4
6
+ before_install:
7
+ - gem install bundler
8
+ - gem update --system
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in undercover.gemspec
6
+ gemspec
@@ -0,0 +1,61 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ undercover (0.1.0)
5
+ imagen (~> 0.1.0)
6
+ rainbow (~> 3.0.0)
7
+ rugged (~> 0.27.0)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ ast (2.4.0)
13
+ coderay (1.1.2)
14
+ diff-lcs (1.3)
15
+ docile (1.3.0)
16
+ imagen (0.1.0)
17
+ parser
18
+ json (2.1.0)
19
+ method_source (0.9.0)
20
+ parser (2.5.1.0)
21
+ ast (~> 2.4.0)
22
+ pry (0.11.3)
23
+ coderay (~> 1.1.0)
24
+ method_source (~> 0.9.0)
25
+ rainbow (3.0.0)
26
+ rake (10.5.0)
27
+ rspec (3.7.0)
28
+ rspec-core (~> 3.7.0)
29
+ rspec-expectations (~> 3.7.0)
30
+ rspec-mocks (~> 3.7.0)
31
+ rspec-core (3.7.1)
32
+ rspec-support (~> 3.7.0)
33
+ rspec-expectations (3.7.0)
34
+ diff-lcs (>= 1.2.0, < 2.0)
35
+ rspec-support (~> 3.7.0)
36
+ rspec-mocks (3.7.0)
37
+ diff-lcs (>= 1.2.0, < 2.0)
38
+ rspec-support (~> 3.7.0)
39
+ rspec-support (3.7.1)
40
+ rugged (0.27.0)
41
+ simplecov (0.16.1)
42
+ docile (~> 1.1)
43
+ json (>= 1.8, < 3)
44
+ simplecov-html (~> 0.10.0)
45
+ simplecov-html (0.10.2)
46
+ simplecov-lcov (0.7.0)
47
+
48
+ PLATFORMS
49
+ ruby
50
+
51
+ DEPENDENCIES
52
+ bundler (~> 1.16)
53
+ pry
54
+ rake (~> 10.0)
55
+ rspec (~> 3.0)
56
+ simplecov
57
+ simplecov-lcov
58
+ undercover!
59
+
60
+ BUNDLED WITH
61
+ 1.16.1
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Jan Grodowski
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,42 @@
1
+ # Undercover
2
+
3
+ [![Build Status](https://travis-ci.org/grodowski/undercover.svg?branch=master)](https://travis-ci.org/grodowski/undercover)
4
+ [![codebeat badge](https://codebeat.co/badges/be548247-2421-4448-bdab-896d13eb02e9)](https://codebeat.co/projects/github-com-grodowski-undercover-master)
5
+
6
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/undercover`. To experiment with that code, run `bin/console` for an interactive prompt.
7
+
8
+ TODO: Delete this and the text above, and describe your gem
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'undercover'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install undercover
25
+
26
+ ## Usage
27
+
28
+ TODO: Write usage instructions here
29
+
30
+ ## Development
31
+
32
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
33
+
34
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
35
+
36
+ ## Contributing
37
+
38
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/undercover.
39
+
40
+ ## License
41
+
42
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "undercover"
5
+ require "pry"
6
+
7
+ Pry.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift("#{__dir__}/../../lib")
5
+
6
+ require 'undercover'
7
+ require 'benchmark'
8
+
9
+ result = 0
10
+ time = Benchmark.realtime do
11
+ result = Undercover::CLI.run(ARGV)
12
+ end
13
+
14
+ puts "Undercover finished in #{time}ms"
15
+ exit result
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH << 'lib'
4
+ require 'imagen'
5
+ require 'rainbow'
6
+ require 'bigdecimal'
7
+ require 'forwardable'
8
+
9
+ require 'undercover/lcov_parser'
10
+ require 'undercover/result'
11
+ require 'undercover/cli'
12
+ require 'undercover/changeset'
13
+ require 'undercover/formatter'
14
+ require 'undercover/options'
15
+
16
+ module Undercover
17
+ class Report
18
+ extend Forwardable
19
+ def_delegators :changeset, :validate
20
+
21
+ attr_reader :changeset,
22
+ :code_structure,
23
+ :lcov,
24
+ :results
25
+
26
+ # TODO: pass merge base as cli argument
27
+ # add dependecy on "options" for all opts (dirs, git_dir, etc)
28
+ def initialize(lcov_report_path, code_dir, git_dir: '.git', compare: nil)
29
+ @lcov = LcovParser.parse(File.open(lcov_report_path))
30
+ # TODO: optimise by building changeset structure only!
31
+ @code_structure = Imagen.from_local(code_dir)
32
+ @changeset = Changeset.new(File.join(code_dir, git_dir), compare).update
33
+ @results = Hash.new { |hsh, key| hsh[key] = [] }
34
+ end
35
+
36
+ def build
37
+ each_result_arg do |filename, coverage, imagen_node|
38
+ results[filename] << Result.new(imagen_node, coverage, filename)
39
+ end
40
+ self
41
+ end
42
+
43
+ # TODO: this is experimental and might be incorrect!
44
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
45
+ def build_warnings
46
+ flagged_results = Set.new
47
+ changeset.each_changed_line do |filepath, line_no|
48
+ dist_from_line_no = lambda do |res|
49
+ return BigDecimal::INFINITY if line_no < res.first_line
50
+ line_no - res.first_line
51
+ end
52
+ dist_from_line_no_sorter = lambda do |res1, res2|
53
+ dist_from_line_no[res1] <=> dist_from_line_no[res2]
54
+ end
55
+
56
+ res = results[filepath].min(&dist_from_line_no_sorter)
57
+ flagged_results << res if res&.uncovered?(line_no)
58
+ end
59
+ flagged_results
60
+ end
61
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
62
+
63
+ def all_results
64
+ results.values.flatten
65
+ end
66
+
67
+ def inspect
68
+ "#<Undercover::Report:#{object_id} results: #{results.size}>"
69
+ end
70
+ alias to_s inspect
71
+
72
+ private
73
+
74
+ def each_result_arg
75
+ matches_path = lambda do |path|
76
+ ->(node) { node.file_path.end_with?(path) }
77
+ end
78
+
79
+ lcov.source_files.each do |filename, coverage|
80
+ code_structure.find_all(matches_path[filename]).each do |node|
81
+ yield(filename, coverage, node)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rugged'
4
+ require 'time'
5
+
6
+ module Undercover
7
+ # Base class for different kinds of input
8
+ class Changeset
9
+ T_ZERO = Time.strptime('0', '%s').freeze
10
+
11
+ extend Forwardable
12
+ include Enumerable
13
+
14
+ attr_reader :files
15
+ def_delegators :files, :each, :'<=>'
16
+
17
+ def initialize(dir, compare_base = nil)
18
+ @dir = dir
19
+ @repo = Rugged::Repository.new(dir)
20
+ @repo.workdir = Pathname.new(dir).dirname.to_s # TODO: can replace?
21
+ @compare_base = compare_base
22
+ @files = {}
23
+ end
24
+
25
+ def update
26
+ full_diff.each_patch do |patch|
27
+ filepath = patch.delta.new_file[:path]
28
+ line_nums = patch.each_hunk.map do |hunk|
29
+ # TODO: optimise this to use line ranges!
30
+ hunk.lines.select(&:addition?).map(&:new_lineno)
31
+ end.flatten
32
+ @files[filepath] = line_nums if line_nums.any?
33
+ end
34
+ self
35
+ end
36
+
37
+ def last_modified
38
+ mod = files.keys.map do |f|
39
+ next T_ZERO unless File.exists?(f)
40
+ File.mtime(File.join(repo.workdir, f))
41
+ end.max
42
+ mod || T_ZERO
43
+ end
44
+
45
+ def each_changed_line
46
+ files.each do |filepath, line_numbers|
47
+ line_numbers.each { |ln| yield filepath, ln }
48
+ end
49
+ end
50
+
51
+ # TODO: refactor to a standalone validator (depending on changeset AND lcov)
52
+ # TODO: add specs
53
+ def validate(lcov_report_path)
54
+ return :no_changes if files.empty?
55
+ return :stale_coverage if last_modified > File.mtime(lcov_report_path)
56
+ end
57
+
58
+ private
59
+
60
+ # Diffs `head` or `head` + `compare_base` (if exists),
61
+ # as it makes sense to run Undercover with the most recent file versions
62
+ def full_diff
63
+ base = compare_base_obj || head
64
+ base.diff(repo.index).merge!(repo.diff_workdir(head))
65
+ end
66
+
67
+ def compare_base_obj
68
+ return nil unless compare_base
69
+ repo.lookup(repo.merge_base(compare_base.to_s, head))
70
+ end
71
+
72
+ def head
73
+ repo.head.target
74
+ end
75
+
76
+ attr_reader :repo, :compare_base
77
+ end
78
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'undercover'
4
+ require 'rainbow'
5
+
6
+ module Undercover
7
+ module CLI
8
+ # TODO: Report calls >parser< for each file instead of
9
+ # traversing the whole project at first!
10
+
11
+ WARNINGS_TO_S = {
12
+ stale_coverage: Rainbow('♻️ Coverage data is older than your' \
13
+ 'latest changes. Re-run tests to update').yellow,
14
+ no_changes: Rainbow('✅ No reportable changes').green
15
+ }.freeze
16
+
17
+ # TODO: add executable in ./bin later
18
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
19
+ def self.run(args)
20
+ opts = Undercover::Options.new.parse(args)
21
+ report = Undercover::Report.new(
22
+ opts.lcov,
23
+ opts.path,
24
+ git_dir: opts.git_dir,
25
+ compare: opts.compare
26
+ ).build
27
+
28
+ error = report.validate(opts.lcov)
29
+ if error
30
+ puts(WARNINGS_TO_S[error])
31
+ return 1
32
+ end
33
+
34
+ warnings = report.build_warnings
35
+ puts Undercover::Formatter.new(warnings)
36
+ warnings.any? ? 1 : 0
37
+ end
38
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Undercover
4
+ class Formatter
5
+ def initialize(results)
6
+ @results = results
7
+ end
8
+
9
+ def to_s
10
+ return success unless @results.any?
11
+ ([warnings_header] + formatted_warnings).join("\n")
12
+ end
13
+
14
+ private
15
+
16
+ def formatted_warnings
17
+ @results.map.with_index(1) do |res, idx|
18
+ "🚨 #{idx}) node `#{res.node.name}` type: #{res.node.class},\n" +
19
+ (' ' * pad_size) + "loc: #{res.file_path_with_lines}," \
20
+ " coverage: #{res.coverage_f * 100}%\n" +
21
+ res.pretty_print
22
+ end
23
+ end
24
+
25
+ def success
26
+ "#{Rainbow('undercover').bold.green}: ✅ No coverage" \
27
+ ' is missing in latest changes'
28
+ end
29
+
30
+ def warnings_header
31
+ "#{Rainbow('undercover').bold.red}: " \
32
+ '👮‍♂️ some methods have no test coverage! Please add specs for' \
33
+ ' methods listed below'
34
+ end
35
+
36
+ def pad_size
37
+ 5 + (@results.size - 1).to_s.length
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Undercover
4
+ LcovParseError = Class.new(StandardError)
5
+
6
+ class LcovParser
7
+ attr_reader :io, :source_files
8
+
9
+ def initialize(lcov_io)
10
+ @io = lcov_io
11
+ @source_files = {}
12
+ end
13
+
14
+ def self.parse(lcov_report_path)
15
+ lcov_io = File.open(lcov_report_path)
16
+ new(lcov_io).parse
17
+ end
18
+
19
+ def parse
20
+ io.each(&method(:parse_line))
21
+ io.close
22
+ self
23
+ end
24
+
25
+ private
26
+
27
+ # rubocop:disable Metrics/MethodLength, Style/SpecialGlobalVars
28
+ def parse_line(line)
29
+ case line
30
+ when /^SF:([\.\/\w]+)/
31
+ @current_filename = $~[1].gsub(/^\.\//, '')
32
+ source_files[@current_filename] = []
33
+ when /^DA:(\d+),(\d+)/
34
+ line_no = $~[1]
35
+ covered = $~[2]
36
+ source_files[@current_filename] << [line_no.to_i, covered.to_i]
37
+ when /^end_of_record$/, /^$/
38
+ @current_filename = nil
39
+ else
40
+ raise LcovParseError, "could not recognise '#{line}' as valid LCOV"
41
+ end
42
+ end
43
+ # rubocop:enable Metrics/MethodLength, Style/SpecialGlobalVars
44
+ end
45
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+ require 'pathname'
5
+
6
+ module Undercover
7
+ class Options
8
+ RUN_MODE = [
9
+ RUN_MODE_DIFF_STRICT = :diff_strict, # warn for changed lines
10
+ # RUN_MODE_DIFF_FILES = :diff_files, # warn for changed whole files
11
+ # RUN_MODE_ALL = :diff_all, # warn for allthethings
12
+ # RUN_MODE_FILES = :files # warn for specific files (cli option)
13
+ ].freeze
14
+
15
+ OUTPUT_FORMATTERS = [
16
+ OUTPUT_STDOUT = :stdout, # outputs warnings to stdout with exit 1
17
+ # OUTPUT_CIRCLEMATOR = :circlemator # posts warnings as review comments
18
+ ].freeze
19
+
20
+ attr_accessor :lcov, :path, :git_dir, :compare
21
+
22
+ def initialize
23
+ # TODO: use run modes
24
+ # TODO: use formatters
25
+ @run_mode = RUN_MODE_DIFF_STRICT
26
+ @enabled_formatters = [OUTPUT_STDOUT]
27
+ # set defaults
28
+ self.lcov = guess_lcov_path
29
+ self.path = '.'
30
+ self.git_dir = '.git'
31
+ end
32
+
33
+ # rubocop:disable Metrics/MethodLength
34
+ def parse(args)
35
+ OptionParser.new do |opts|
36
+ opts.banner = 'Usage: undercover [options]'
37
+
38
+ opts.on_tail('-h', '--help', 'Prints this help') do
39
+ puts(opts)
40
+ exit
41
+ end
42
+
43
+ opts.on_tail('--version', 'Show version') do
44
+ puts Version
45
+ exit
46
+ end
47
+
48
+ lcov_path_option(opts)
49
+ project_path_option(opts)
50
+ git_dir_option(opts)
51
+ compare_option(opts)
52
+ # TODO: parse dem other options and assign to self
53
+ # --quiet (skip progress bar)
54
+ # --exit-status (do not print report, just exit)
55
+ # --ruby-version (string, like '2.4.4', how to support in parser?)
56
+ end.parse(args)
57
+ self
58
+ end
59
+ # rubocop:enable Metrics/MethodLength
60
+
61
+ private
62
+
63
+ def lcov_path_option(parser)
64
+ parser.on('-l', '--lcov path', 'LCOV report file path') do |path|
65
+ self.lcov = path
66
+ end
67
+ end
68
+
69
+ def project_path_option(parser)
70
+ parser.on('-p', '--path path', 'Project directory') do |path|
71
+ self.path = path
72
+ end
73
+ end
74
+
75
+ def git_dir_option(parser)
76
+ desc = 'Override `.git` with a custom directory'
77
+ parser.on('-g', '--git-dir dir', desc) do |dir|
78
+ self.git_dir = dir
79
+ end
80
+ end
81
+
82
+ def compare_option(parser)
83
+ desc = 'Generate coverage warnings for all changes after `ref`'
84
+ parser.on('-c', '--compare ref', desc) do |ref|
85
+ self.compare = ref
86
+ end
87
+ end
88
+
89
+ def guess_lcov_path
90
+ pwd = Pathname.pwd
91
+ File.join(pwd, 'coverage', 'lcov', "#{pwd.split.last}.lcov")
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Undercover
6
+ class Result
7
+ extend Forwardable
8
+
9
+ attr_reader :node, :coverage, :file_path
10
+
11
+ def_delegators :node, :first_line, :last_line
12
+
13
+ def initialize(node, file_cov, file_path)
14
+ @node = node
15
+ @coverage = file_cov.select do |ln, _|
16
+ ln > first_line && ln < last_line
17
+ end
18
+ @file_path = file_path
19
+ end
20
+
21
+ # TODO: make DRY
22
+ def non_code?(line_no)
23
+ line_cov = coverage.find { |ln, _cov| ln == line_no }
24
+ !line_cov
25
+ end
26
+
27
+ def covered?(line_no)
28
+ line_cov = coverage.find { |ln, _cov| ln == line_no }
29
+ line_cov && line_cov[1].positive?
30
+ end
31
+
32
+ def uncovered?(line_no)
33
+ line_cov = coverage.find { |ln, _cov| ln == line_no }
34
+ line_cov && line_cov[1].zero?
35
+ end
36
+
37
+ def coverage_f
38
+ covered = coverage.reduce(0) do |sum, (_, cov)|
39
+ sum + [[0, cov].max, 1].min
40
+ end
41
+ (covered.to_f / coverage.size).round(4)
42
+ end
43
+
44
+ # TODO: create a formatter interface instead and add some tests.
45
+ # TODO: re-enable rubocops
46
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
47
+ #
48
+ # Zips coverage data (that doesn't include any non-code lines) with
49
+ # full source for given code fragment (this includes non-code lines!)
50
+ def pretty_print_lines
51
+ cov_enum = coverage.each
52
+ cov_source_lines = (node.first_line..node.last_line).map do |line_no|
53
+ cov_line_no = begin
54
+ cov_enum.peek[0]
55
+ rescue StopIteration
56
+ -1
57
+ end
58
+ cov_enum.next[1] if cov_line_no == line_no
59
+ end
60
+ cov_source_lines.zip(node.source_lines_with_numbers)
61
+ end
62
+
63
+ # TODO: move to formatter interface instead!
64
+ def pretty_print
65
+ pad = node.last_line.to_s.length
66
+ pretty_print_lines.map do |covered, (num, line)|
67
+ formatted_line = "#{num.to_s.rjust(pad)}: #{line}"
68
+ if line.strip.length.zero?
69
+ Rainbow(formatted_line).darkgray.dark
70
+ elsif covered.nil?
71
+ Rainbow(formatted_line).darkgray.dark + \
72
+ Rainbow(' hits: n/a').italic.darkgray.dark
73
+ elsif covered.positive?
74
+ Rainbow(formatted_line).green + \
75
+ Rainbow(" hits: #{covered}").italic.darkgray.dark
76
+ elsif covered.zero?
77
+ Rainbow(formatted_line).red + \
78
+ Rainbow(" hits: #{covered}").italic.darkgray.dark
79
+ end
80
+ end.join("\n")
81
+ end
82
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
83
+
84
+ def file_path_with_lines
85
+ "#{file_path}:#{first_line}:#{last_line}"
86
+ end
87
+
88
+ def inspect
89
+ "#<Undercover::Report::Result:#{object_id}" \
90
+ " name: #{node.name}, coverage: #{coverage_f}>"
91
+ end
92
+ alias to_s inspect
93
+ end
94
+ end
@@ -0,0 +1,3 @@
1
+ module Undercover
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,33 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "undercover/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "undercover"
8
+ spec.version = Undercover::VERSION
9
+ spec.authors = ["Jan Grodowski"]
10
+ spec.email = ["jgrodowski@gmail.com"]
11
+
12
+ spec.summary = %q{Actionable code coverage - detects untested code blocks in recent changes}
13
+ spec.homepage = "https://github.com/grodowski/undercover"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "bin"
20
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_dependency 'imagen', '~> 0.1.0'
24
+ spec.add_dependency 'rainbow', '~> 3.0.0'
25
+ spec.add_dependency 'rugged', '~> 0.27.0'
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.16"
28
+ spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency "rspec", "~> 3.0"
30
+ spec.add_development_dependency 'pry'
31
+ spec.add_development_dependency 'simplecov'
32
+ spec.add_development_dependency 'simplecov-lcov'
33
+ end
metadata ADDED
@@ -0,0 +1,194 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: undercover
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jan Grodowski
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-05-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: imagen
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rainbow
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 3.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 3.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rugged
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.27.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.27.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.16'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.16'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: simplecov-lcov
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ description:
140
+ email:
141
+ - jgrodowski@gmail.com
142
+ executables:
143
+ - console
144
+ - setup
145
+ - undercover
146
+ extensions: []
147
+ extra_rdoc_files: []
148
+ files:
149
+ - ".gitignore"
150
+ - ".rspec"
151
+ - ".rubocop.yml"
152
+ - ".travis.yml"
153
+ - Gemfile
154
+ - Gemfile.lock
155
+ - LICENSE.txt
156
+ - README.md
157
+ - Rakefile
158
+ - bin/console
159
+ - bin/setup
160
+ - bin/undercover
161
+ - lib/undercover.rb
162
+ - lib/undercover/changeset.rb
163
+ - lib/undercover/cli.rb
164
+ - lib/undercover/formatter.rb
165
+ - lib/undercover/lcov_parser.rb
166
+ - lib/undercover/options.rb
167
+ - lib/undercover/result.rb
168
+ - lib/undercover/version.rb
169
+ - undercover.gemspec
170
+ homepage: https://github.com/grodowski/undercover
171
+ licenses:
172
+ - MIT
173
+ metadata: {}
174
+ post_install_message:
175
+ rdoc_options: []
176
+ require_paths:
177
+ - lib
178
+ required_ruby_version: !ruby/object:Gem::Requirement
179
+ requirements:
180
+ - - ">="
181
+ - !ruby/object:Gem::Version
182
+ version: '0'
183
+ required_rubygems_version: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ requirements: []
189
+ rubyforge_project:
190
+ rubygems_version: 2.6.14.1
191
+ signing_key:
192
+ specification_version: 4
193
+ summary: Actionable code coverage - detects untested code blocks in recent changes
194
+ test_files: []