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 +7 -0
- data/CHANGELOG.md +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +64 -0
- data/RELEASE.md +51 -0
- data/SECURITY.md +21 -0
- data/bin/uncov +9 -0
- data/lib/uncov/cache.rb +13 -0
- data/lib/uncov/cli.rb +53 -0
- data/lib/uncov/configuration.rb +17 -0
- data/lib/uncov/finder/file_system.rb +24 -0
- data/lib/uncov/finder/git.rb +16 -0
- data/lib/uncov/finder/git_base.rb +22 -0
- data/lib/uncov/finder/git_diff.rb +42 -0
- data/lib/uncov/finder/no_cov.rb +23 -0
- data/lib/uncov/finder/simple_cov.rb +42 -0
- data/lib/uncov/finder.rb +45 -0
- data/lib/uncov/formatter/terminal.rb +40 -0
- data/lib/uncov/formatter.rb +15 -0
- data/lib/uncov/report/diff_lines.rb +30 -0
- data/lib/uncov/report/file/line.rb +9 -0
- data/lib/uncov/report/file.rb +30 -0
- data/lib/uncov/report.rb +22 -0
- data/lib/uncov/version.rb +5 -0
- data/lib/uncov.rb +57 -0
- metadata +126 -0
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
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
data/lib/uncov/cache.rb
ADDED
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
|
data/lib/uncov/finder.rb
ADDED
@@ -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
|
data/lib/uncov/report.rb
ADDED
@@ -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
|
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: []
|