uncov 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2d0159da32804599de7afca67329f3d883857c049a2a681028d19cf940aec6be
4
+ data.tar.gz: 7aba6e5fddd2025ca51f9038770f41bce2b9b7426201d16c568e9a565b76fb78
5
+ SHA512:
6
+ metadata.gz: 6e84d73647ed6996d0d3d85f05f87542e3f2820008332d842a3feb7d9b4d65b754bc986fcff2c0ae019bbb6e1e474de9cd09ffa82ba9591743af6f5c3e8a5e03
7
+ data.tar.gz: 2151c2f5d2c43ef2a8e1095867a8238f47030a4ab9b4b5a136b8fde9cb1c015e3e6de98410cf95b68784d9af9f3e369c5f6b9710a666b6280da75d4226157b60
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## [0.1.1] - 2025-04-28
4
+
5
+ - releasing...
6
+
7
+
8
+ ## [0.1.0] - 2025-04-28
9
+
10
+ - uncov -r diff_lines -f termianl
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright © 2025
4
+
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the “Software”), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # Uncov
2
+
3
+ Uncov analyzes test coverage for changed files in your Git repository, helping you ensure that all your recent changes are properly tested.
4
+
5
+ ## Features
6
+
7
+ - Compare your working tree to a target branch
8
+ - Identify changed Ruby files
9
+ - Check test coverage for those changes using SimpleCov
10
+ - Run tests automatically for files without coverage data
11
+ - Generate reports of uncovered lines in changed files
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ gem install uncov
17
+ ```
18
+
19
+ Or add to your Gemfile:
20
+ ```ruby
21
+ gem 'uncov'
22
+ ```
23
+
24
+ ## Usage
25
+ Basic usage:
26
+ ```bash
27
+ uncov
28
+ ```
29
+ With options:
30
+ ```bash
31
+ uncov --branch develop --path custom/coverage/path
32
+ ```
33
+ Options
34
+ - `-t`, `--target BRANCH`: Target branch for comparison (default: main)
35
+ - `-c`, `--command COMMAND`: Test command to run (default: bundle exec rake test)
36
+ - `-p`, `--path PATH`: Path to SimpleCov results (default: coverage/.resultset.json)
37
+ - `-h`, `--help`: Display help
38
+ - `-v`, `--version`: Display version
39
+
40
+ ## Requirements
41
+
42
+ - Ruby 2.7+
43
+ - A Git repository
44
+ - SimpleCov for test coverage
45
+
46
+ ## Development
47
+ - `docker-compose run uncov` to enter dev container,
48
+ - `bundle` to install dependencies,
49
+ - `bundle exec rake` to run test and lint,
50
+ - `bin/uncov` to see uncovered changes (check itself)
51
+
52
+ ## Contributing
53
+ Bug reports and pull requests are welcome on GitHub at https://github.com/mpapis/uncov.
54
+
55
+ ## Release Process
56
+ This project uses an automated release process managed through GitHub Actions. For detailed information about contributing and the release workflow, please see [RELEASE.md](RELEASE.md).
57
+
58
+ Maintainers can trigger new releases through the GitHub Actions interface with minimal manual intervention, following Semantic Versioning principles.
59
+
60
+ ## Security
61
+ This project has security practices, please see [SECURITY.md](SECURITY.md).
62
+
63
+ ## License
64
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/RELEASE.md ADDED
@@ -0,0 +1,51 @@
1
+ # Release Process
2
+
3
+ This gem uses GitHub Actions for continuous integration and automated releases. Here's how the release process works:
4
+
5
+ ## For Contributors
6
+
7
+ 1. Develop features in feature branches
8
+ 2. Open pull requests against the `develop` branch
9
+ 3. Ensure CI passes and get code review approval
10
+ 4. Your changes will be included in the next release
11
+
12
+ ## For Maintainers
13
+
14
+ ### Preparing a Release
15
+
16
+ 1. Go to the "Actions" tab in the GitHub repository
17
+ 2. Select the "Prepare Release" workflow
18
+ 3. Click "Run workflow"
19
+ 4. Choose the version bump type (patch, minor, or major) following SemVer
20
+ 5. Optionally select "Create pre-release" for release candidates
21
+ 6. Click "Run workflow"
22
+
23
+ This will:
24
+ - Increment the version number appropriately
25
+ - Generate a changelog based on PRs since the last release
26
+ - Create a release branch and open a PR to main
27
+
28
+ ### Finalizing a Release
29
+
30
+ 1. Review and merge the auto-generated release PR into `main`
31
+ 2. The system will automatically:
32
+ - Create a git tag for the release
33
+ - Run tests one final time
34
+ - Build and publish the gem to RubyGems
35
+ - Create a GitHub Release with the changelog
36
+
37
+ ### Pre-releases
38
+
39
+ For pre-releases (e.g. v1.2.0.pre1):
40
+ 1. Follow the normal preparation process but check "Create pre-release"
41
+ 2. The gem will be published to RubyGems as a pre-release version
42
+ 3. Users can install it with `gem install uncov --pre`
43
+
44
+ ## Manual Override
45
+
46
+ If you need to create a release manually:
47
+ 1. Update version in `lib/uncov/version.rb`
48
+ 2. Update `CHANGELOG.md`
49
+ 3. Commit these changes to `main`
50
+ 4. Create and push a git tag: `git tag -a v1.2.3 -m "Release v1.2.3" && git push origin v1.2.3`
51
+ 5. The tag will trigger the release workflow
data/SECURITY.md ADDED
@@ -0,0 +1,21 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ If you discover a security vulnerability within uncov, please email [mpapis@gmail.com](mailto:mpapis@gmail.com).
6
+ All security vulnerabilities will be promptly addressed.
7
+
8
+ ## Security Practices
9
+
10
+ This project follows these security practices:
11
+
12
+ 1. **Code Review**:
13
+ - All pull requests must be reviewed by a project maintainer
14
+ - Workflow files cannot be modified without explicit review from core maintainers
15
+
16
+ ## Releasers
17
+
18
+ Only the following GitHub accounts are authorized to release new versions:
19
+ - @mpapis
20
+
21
+ The rubygems release process uses [trusted publishing](https://guides.rubygems.org/trusted-publishing/).
data/bin/uncov ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ # :nocov:
6
+ require 'uncov'
7
+ # :nocov:
8
+
9
+ Uncov::CLI.start(ARGV)
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A caching helper for methods
4
+ module Uncov::Cache
5
+ protected
6
+
7
+ def cache(key)
8
+ @cache ||= {}
9
+ return @cache[key] if @cache.key?(key)
10
+
11
+ @cache[key] = yield
12
+ end
13
+ end
data/lib/uncov/cli.rb ADDED
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+
5
+ # provide terminal interface for uncov
6
+ class Uncov::CLI
7
+ def self.start(args)
8
+ new.start(args)
9
+ end
10
+
11
+ def start(args)
12
+ parse_options(args)
13
+ report = Uncov::Report.new
14
+ Uncov::Formatter.output(report)
15
+ exit(report.covered? ? 0 : 1)
16
+ end
17
+
18
+ private
19
+
20
+ def parse_options(args) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
21
+ parser = OptionParser.new do |opts| # rubocop:disable Metrics/BlockLength
22
+ opts.banner = 'Usage: uncov [options]'
23
+ opts.on('-r', '--report TYPE', 'Report type to generate (git_diff)') do |type|
24
+ Uncov.configuration.report = type.to_sym
25
+ end
26
+ opts.on('-t', '--target BRANCH', 'Target branch for comparison') do |branch|
27
+ Uncov.configuration.git_diff_target = branch
28
+ end
29
+ opts.on('-c', '--command COMMAND', 'Test command to run SimpleCov') do |command|
30
+ Uncov.configuration.test_command = command
31
+ end
32
+ opts.on('-p', '--path PATH', 'Target filesystem path') do |path|
33
+ Uncov.configuration.path = path
34
+ end
35
+ opts.on('-f', '--formater FORMATTER', 'Formatter for output') do |formatter|
36
+ Uncov.configuration.output_format = formatter.to_sym
37
+ end
38
+ opts.on('--simplecov-path PATH', 'Path to SimpleCov results') do |path|
39
+ Uncov.configuration.simplecov_output_path = path
40
+ end
41
+ opts.on('-h', '--help', 'Print this help') do
42
+ puts opts
43
+ puts "uncov #{Uncov::VERSION} by Michal Papis <mpapis@gmail.com>"
44
+ exit
45
+ end
46
+ opts.on('-v', '--version', 'Show version') do
47
+ puts "uncov #{Uncov::VERSION} by Michal Papis <mpapis@gmail.com>"
48
+ exit
49
+ end
50
+ end
51
+ parser.parse!(args)
52
+ end
53
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # handle configuration for uncov
4
+ class Uncov::Configuration
5
+ FILE_MATCH_FLAGS = File::FNM_EXTGLOB | File::FNM_PATHNAME | File::FNM_DOTMATCH
6
+ attr_accessor :git_diff_target, :report, :output_format, :path, :relevant_files, :simplecov_output_path, :test_command
7
+
8
+ def initialize
9
+ @git_diff_target = 'HEAD'
10
+ @test_command = 'COVERAGE=true bundle exec rake test'
11
+ @simplecov_output_path = 'coverage/coverage.json'
12
+ @path = '.'
13
+ @relevant_files = ['{bin,exe,exec}/*', '{app,lib}/**/*.{rake,rb}']
14
+ @report = :diff_lines
15
+ @output_format = :terminal
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # collect files and their lines content from system
4
+ class Uncov::Finder::FileSystem
5
+ include Uncov::Cache
6
+
7
+ def files = all_files.to_h { |file_name| [file_name, lines_proc(file_name)] }
8
+
9
+ private
10
+
11
+ def all_files
12
+ Uncov.configuration.relevant_files.flat_map do |expresion|
13
+ Dir
14
+ .glob(expresion, Uncov::Configuration::FILE_MATCH_FLAGS, base: Uncov.configuration.path)
15
+ .select { |f| File.file?(f) }
16
+ end
17
+ end
18
+
19
+ def lines_proc(file_name) = -> { cache(file_name) { read_lines(file_name) } }
20
+
21
+ def read_lines(file_name)
22
+ File.readlines(file_name).each_with_index.to_h { |line, line_index| [line_index + 1, line.rstrip] }
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'git_base'
4
+
5
+ # collect list of files stored in git
6
+ module Uncov::Finder::Git
7
+ class << self
8
+ include Uncov::Finder::GitBase
9
+
10
+ def files
11
+ open_repo.ls_files.keys.filter_map do |file_name|
12
+ [file_name, true] if relevant_file?(file_name)
13
+ end.to_h
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git'
4
+
5
+ # common parts for git finders
6
+ module Uncov::Finder::GitBase
7
+ protected
8
+
9
+ def relevant_file?(path)
10
+ Uncov.configuration.relevant_files.any? do |pattern|
11
+ File.fnmatch?(pattern, path, Uncov::Configuration::FILE_MATCH_FLAGS)
12
+ end
13
+ end
14
+
15
+ def open_repo
16
+ ::Git.open(Uncov.configuration.path)
17
+ rescue ArgumentError => e
18
+ raise Uncov::NotGitRepoError, Uncov.configuration.path if e.message.end_with?(' is not in a git working tree')
19
+
20
+ raise
21
+ end
22
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'git_base'
4
+ require 'git_diff_parser'
5
+
6
+ # collect list of changed files and their added lines (removed do not impact coverage)
7
+ module Uncov::Finder::GitDiff
8
+ class << self
9
+ include Uncov::Finder::GitBase
10
+
11
+ def files
12
+ git_diff.filter_map do |file_diff|
13
+ [file_diff.path, changed_lines(file_diff)] if relevant_file?(file_diff.path)
14
+ end.to_h
15
+ end
16
+
17
+ private
18
+
19
+ def changed_lines(file_diff)
20
+ GitDiffParser.parse(file_diff.patch).flat_map do |patch|
21
+ patch.changed_lines.map do |changed_line|
22
+ [changed_line.number, nil] if changed_line.content[0] == '+'
23
+ end
24
+ end.compact.to_h
25
+ end
26
+
27
+ def git_diff
28
+ repo = open_repo
29
+ target =
30
+ case git_diff_target
31
+ when 'HEAD'
32
+ git_diff_target
33
+ else
34
+ repo.branches[git_diff_target] or raise NotGitBranchError, git_diff_target
35
+ end
36
+
37
+ repo.diff(target)
38
+ end
39
+
40
+ def git_diff_target = Uncov.configuration.git_diff_target
41
+ end
42
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ # collect nocov information from files
4
+ class Uncov::Finder::NoCov
5
+ include Uncov::Cache
6
+
7
+ def files(all_files)
8
+ all_files.to_h { |file_name, lines| [file_name, nocov_proc(file_name, lines)] }
9
+ end
10
+
11
+ private
12
+
13
+ def nocov_proc(file_name, lines_proc) = -> { cache(file_name) { read_nocov(lines_proc) } }
14
+
15
+ def read_nocov(lines_proc)
16
+ nocov = false
17
+ lines_proc.call.filter_map do |number, line|
18
+ line_nocov = line.strip.start_with?('# :nocov:')
19
+ nocov = !nocov if line_nocov
20
+ [number, true] if nocov || line_nocov # still true on disabling line
21
+ end.to_h
22
+ end
23
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ # collect coverage information, regenerates report if any trigger_files are newer then the report
6
+ module Uncov::Finder::SimpleCov
7
+ class << self
8
+ def files(trigger_files = [])
9
+ regenerate_report if requires_regeneration?(trigger_files)
10
+ raise MissingSimpleCovReport, coverage_path unless File.exist?(coverage_path)
11
+
12
+ coverage.transform_values { |file_coverage| covered_lines(file_coverage) }
13
+ end
14
+
15
+ private
16
+
17
+ def requires_regeneration?(trigger_files)
18
+ return true unless File.exist?(coverage_path)
19
+ return false if trigger_files.empty?
20
+
21
+ coverage_path_mtime = File.mtime(coverage_path)
22
+ trigger_files.any? { |file_name| File.exist?(file_name) && File.mtime(file_name) > coverage_path_mtime }
23
+ end
24
+
25
+ def regenerate_report = Dir.chdir(Uncov.configuration.path) { system(Uncov.configuration.test_command) }
26
+
27
+ def coverage
28
+ root_path = "#{File.absolute_path(Uncov.configuration.path)}/"
29
+ parsed = JSON.parse(File.read(coverage_path))
30
+ coverage = parsed['coverage'] || parsed.order_by { |suite| suite['timestamp'] }.last['coverage']
31
+ coverage.transform_keys { |key| key.delete_prefix(root_path) }
32
+ end
33
+
34
+ def covered_lines(file_coverage)
35
+ file_coverage['lines'].each_with_index.filter_map do |coverage, line_index|
36
+ [line_index + 1, coverage.positive?] if coverage
37
+ end.to_h
38
+ end
39
+
40
+ def coverage_path = File.join(Uncov.configuration.path, Uncov.configuration.simplecov_output_path)
41
+ end
42
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # collects information about the files from different sources
4
+ class Uncov::Finder
5
+ include Uncov::Cache
6
+
7
+ def initialize(simple_cov_trigger) = @simple_cov_trigger = simple_cov_trigger
8
+ def git_file?(file_name) = git_files[file_name]
9
+ def git_file_names = git_files.keys
10
+ def git_diff_file_names = git_diff_files.keys
11
+ def git_diff_file_line?(file_name, line_number) = git_diff_files.dig(file_name, line_number)
12
+ def git_diff_file_lines(file_name) = git_diff_files[file_name]
13
+ def file_system_file_line(file_name, line_number) = file_system_files[file_name]&.call&.dig(line_number)
14
+ def no_cov_file_line?(file_name, line_number) = no_cov_files[file_name]&.call&.dig(line_number)
15
+ def simple_cov_file_line?(file_name, line_number) = simple_cov_files.dig(file_name, line_number)
16
+
17
+ def debug
18
+ {
19
+ git_files:,
20
+ git_diff_files:,
21
+ file_system_files: file_system_files.transform_values(&:call),
22
+ no_cov_files: no_cov_files.transform_values(&:call),
23
+ simple_cov_files:
24
+ }
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :simple_cov_trigger
30
+
31
+ def git_files = cache(:git_files) { Uncov::Finder::Git.files }
32
+ def git_diff_files = cache(:git_diff_files) { Uncov::Finder::GitDiff.files }
33
+ def file_system_files = cache(:file_system_files) { Uncov::Finder::FileSystem.new.files }
34
+ def no_cov_files = cache(:no_cov_files) { Uncov::Finder::NoCov.new.files(file_system_files) }
35
+ def simple_cov_files = cache(:simple_cov_files) { Uncov::Finder::SimpleCov.files(simple_cov_trigger_files) }
36
+
37
+ def simple_cov_trigger_files
38
+ case simple_cov_trigger
39
+ when :git
40
+ git_file_names
41
+ when :git_diff
42
+ git_diff_file_names
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'colorize'
4
+ require 'forwardable'
5
+
6
+ # print report to terminal with colors
7
+ class Uncov::Formatter::Terminal
8
+ attr_reader :report
9
+
10
+ def initialize(report)
11
+ @report = report
12
+ end
13
+
14
+ def output
15
+ if report.covered?
16
+ puts 'All changed files have 100% test coverage!'.green
17
+ return
18
+ end
19
+
20
+ puts "Found #{report.uncovered_files.size} files with uncovered changes:".yellow
21
+ output_files
22
+ puts format("\nOverall coverage of changes: %.2f%%", report.coverage).yellow
23
+ end
24
+
25
+ def output_files = report.uncovered_files.each { |file_coverage| output_file(file_coverage) }
26
+
27
+ def output_file(file_coverage)
28
+ puts format(
29
+ "\n%<name>s -> %<coverage>.2f%% changes covered, uncovered lines:",
30
+ name: file_coverage.file_name,
31
+ coverage: file_coverage.coverage
32
+ ).yellow
33
+ nl = number_length(file_coverage)
34
+ file_coverage.uncovered_lines.each do |line|
35
+ puts format("%#{nl}d: %s", line.number, line.content).red
36
+ end
37
+ end
38
+
39
+ def number_length(file_coverage) = file_coverage.uncovered_lines.last.number.to_s.length
40
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # chose formater to output the report
4
+ module Uncov::Formatter
5
+ class << self
6
+ def output(report)
7
+ case Uncov.configuration.output_format
8
+ when :terminal
9
+ Uncov::Formatter::Terminal.new(report).output
10
+ else
11
+ raise UnsupportedFormatterError, Uncov.configuration.output_format
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # report only files lines from the diff
4
+ module Uncov::Report::DiffLines
5
+ class << self
6
+ def files(report)
7
+ report.git_diff_file_names.map do |file_name|
8
+ Uncov::Report::File.new(
9
+ file_name:,
10
+ git: true,
11
+ lines: lines(report, file_name)
12
+ )
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def lines(report, file_name)
19
+ report.git_diff_file_lines(file_name).keys.map do |line_number|
20
+ Uncov::Report::File::Line.new(
21
+ number: line_number,
22
+ content: report.file_system_file_line(file_name, line_number),
23
+ no_cov: report.no_cov_file_line?(file_name, line_number),
24
+ simple_cov: report.simple_cov_file_line?(file_name, line_number),
25
+ git_diff: true
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # represents file line coverage in report
4
+ class Uncov::Report::File
5
+ Line = Struct.new('Line', :number, :content, :simple_cov, :no_cov, :git_diff) do
6
+ def covered? = simple_cov != false || no_cov
7
+ def relevant? = !no_cov
8
+ end
9
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # represents file coverage in report
4
+ class Uncov::Report::File
5
+ include Uncov::Cache
6
+
7
+ attr_reader :file_name, :lines, :git
8
+
9
+ def initialize(file_name:, lines:, git:)
10
+ @file_name = file_name
11
+ @lines = lines
12
+ @git = git
13
+ end
14
+
15
+ def coverage
16
+ cache(:coverage) do
17
+ if relevant_lines.count.zero?
18
+ 100.0
19
+ else
20
+ (covered_lines.count.to_f / relevant_lines.count * 100).round(2)
21
+ end
22
+ end
23
+ end
24
+
25
+ def covered? = lines.all?(&:covered?)
26
+ def changed_lines = lines.select(&:git_diff)
27
+ def covered_lines = lines.select(&:covered?)
28
+ def uncovered_lines = lines.reject(&:covered?)
29
+ def relevant_lines = lines.select(&:relevant?)
30
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'cache'
4
+
5
+ # calculated coverage report for configured report type
6
+ class Uncov::Report
7
+ include Uncov::Cache
8
+
9
+ def files
10
+ cache(:files) do
11
+ case Uncov.configuration.report
12
+ when :diff_lines
13
+ finder = Uncov::Finder.new(:git_diff)
14
+ Uncov::Report::DiffLines.files(finder)
15
+ end
16
+ end
17
+ end
18
+
19
+ def uncovered_files = cache(:uncovered_files) { files.reject(&:covered?) }
20
+ def coverage = cache(:coverage) { files.sum(&:coverage) / files.size }
21
+ def covered? = files.all?(&:covered?)
22
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uncov
4
+ VERSION = '0.1.1'
5
+ end
data/lib/uncov.rb ADDED
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir["#{File.dirname(__FILE__)}/**/*.rb"]
4
+ .sort_by { |f| f.count('/') }
5
+ .each { |f| require f unless f.end_with?('lib/uncov.rb') }
6
+
7
+ # uncover missing code coverage by tests
8
+ module Uncov
9
+ class << self
10
+ attr_accessor :configuration
11
+
12
+ def configure
13
+ self.configuration ||= Configuration.new
14
+ yield(configuration) if block_given?
15
+ end
16
+
17
+ def reset
18
+ self.configuration = Configuration.new
19
+ end
20
+ end
21
+
22
+ class Error < StandardError; end
23
+ class GitError < Error; end
24
+ class SimpleCovError < Error; end
25
+ class FormatterError < Error; end
26
+
27
+ class NotGitRepoError < GitError
28
+ attr_reader :path
29
+
30
+ def initialize(path) = @path = path
31
+ def message = "#{path.inspect} is not in a git working tree"
32
+ end
33
+
34
+ class NotGitBranchError < GitError
35
+ attr_reader :target_branch
36
+
37
+ def initialize(target_branch) = @target_branch = target_branch
38
+ def message = "Target branch #{target_branch.inspect} not found locally or in remote"
39
+ end
40
+
41
+ class MissingSimpleCovReport < SimpleCovError
42
+ attr_reader :coverage_path
43
+
44
+ def initialize(coverage_path) = @coverage_path = coverage_path
45
+ def message = "SimpleCov results not found at #{coverage_path.inspect}"
46
+ end
47
+
48
+ class UnsupportedFormatterError < FormatterError
49
+ attr_reader :output_format
50
+
51
+ def initialize(output_format) = @output_format = output_format
52
+ def message = "#{output_format.inspect} is not a supported formatter"
53
+ end
54
+ end
55
+
56
+ # Initialize with defaults
57
+ Uncov.configure
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: uncov
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Michał Papis
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: colorize
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: git
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: git_diff_parser
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '4.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '4.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: logger
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.7'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.7'
68
+ description: uncov compares your current branch with a target branch, identifies changed
69
+ files, and reports on test coverage for those changes
70
+ email:
71
+ - mpapis@gmail.com
72
+ executables:
73
+ - uncov
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - CHANGELOG.md
78
+ - LICENSE.txt
79
+ - README.md
80
+ - RELEASE.md
81
+ - SECURITY.md
82
+ - bin/uncov
83
+ - lib/uncov.rb
84
+ - lib/uncov/cache.rb
85
+ - lib/uncov/cli.rb
86
+ - lib/uncov/configuration.rb
87
+ - lib/uncov/finder.rb
88
+ - lib/uncov/finder/file_system.rb
89
+ - lib/uncov/finder/git.rb
90
+ - lib/uncov/finder/git_base.rb
91
+ - lib/uncov/finder/git_diff.rb
92
+ - lib/uncov/finder/no_cov.rb
93
+ - lib/uncov/finder/simple_cov.rb
94
+ - lib/uncov/formatter.rb
95
+ - lib/uncov/formatter/terminal.rb
96
+ - lib/uncov/report.rb
97
+ - lib/uncov/report/diff_lines.rb
98
+ - lib/uncov/report/file.rb
99
+ - lib/uncov/report/file/line.rb
100
+ - lib/uncov/version.rb
101
+ homepage: https://github.com/mpapis/uncov
102
+ licenses:
103
+ - MIT
104
+ metadata:
105
+ homepage_uri: https://github.com/mpapis/uncov
106
+ source_code_uri: https://github.com/mpapis/uncov
107
+ changelog_uri: https://github.com/mpapis/uncov/blob/develop/CHANGELOG.md
108
+ rubygems_mfa_required: 'true'
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 3.2.0
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubygems_version: 3.6.7
124
+ specification_version: 4
125
+ summary: Analyze test coverage for changed files in your git repository
126
+ test_files: []