circleci-coverage_reporter 0.1.0
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/.gitignore +10 -0
- data/.rspec +2 -0
- data/.rubocop.yml +11 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +57 -0
- data/Rakefile +15 -0
- data/bin/setup +8 -0
- data/circle.yml +9 -0
- data/circleci-coverage_reporter.gemspec +24 -0
- data/lib/circleci/coverage_reporter.rb +29 -0
- data/lib/circleci/coverage_reporter/abstract_reporter.rb +44 -0
- data/lib/circleci/coverage_reporter/abstract_result.rb +16 -0
- data/lib/circleci/coverage_reporter/abstract_vcs_client.rb +21 -0
- data/lib/circleci/coverage_reporter/artifact.rb +16 -0
- data/lib/circleci/coverage_reporter/build.rb +17 -0
- data/lib/circleci/coverage_reporter/client.rb +103 -0
- data/lib/circleci/coverage_reporter/configuration.rb +64 -0
- data/lib/circleci/coverage_reporter/github_client.rb +26 -0
- data/lib/circleci/coverage_reporter/rake_task.rb +15 -0
- data/lib/circleci/coverage_reporter/report.rb +72 -0
- data/lib/circleci/coverage_reporter/runner.rb +60 -0
- data/lib/circleci/coverage_reporter/simplecov/build_result.rb +36 -0
- data/lib/circleci/coverage_reporter/simplecov/current_result.rb +48 -0
- data/lib/circleci/coverage_reporter/simplecov/reporter.rb +44 -0
- data/lib/circleci/coverage_reporter/version.rb +13 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5d57e277c4ae42ade0ad0c319de9c2a17699c8e5
|
4
|
+
data.tar.gz: 3861f10bb0fbc4428a9c90f2e5af9081f224d738
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9d2e5c7d83571b05ec2b81451d646242e986cb9f8b1a9cb30194e10201c72b6667c5e89c2f6b01e4c289a6b74237e92c83178925810bf1b5dd4437a01b5a5471
|
7
|
+
data.tar.gz: acb7bba6f2a64c0ae0ae82b11ba5f38684fc7c185dc25ec41b7e2be77243446940e0a2dd340d97202d12e0833c31f9095b7ce0358ca1a6938efcdd359e22781d
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Change Log
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
5
|
+
and this project adheres to [Semantic Versioning](http://semver.org/).
|
6
|
+
|
7
|
+
## 0.0.0 - 2017-03-12
|
8
|
+
### Added
|
9
|
+
|
10
|
+
- Initial release
|
data/Gemfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
group :development, :test do
|
6
|
+
gem 'rake', '~> 11.0'
|
7
|
+
gem 'rspec', '~> 3.5'
|
8
|
+
gem 'rspec_junit_formatter', '~> 0.2', require: false
|
9
|
+
gem 'rubocop', '~> 0.42'
|
10
|
+
gem 'simplecov', '~> 0.13'
|
11
|
+
gem 'yard', '~> 0.9'
|
12
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Increments inc,
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# CircleCI::CoverageReporter
|
2
|
+
|
3
|
+
[](https://circleci.com/gh/increments/circleci-coverage_reporter)
|
4
|
+
|
5
|
+
**CircleCI::CoverageReporter** reports test coverage to your GitHub repository.
|
6
|
+
|
7
|
+
## Getting started
|
8
|
+
|
9
|
+
1. Add CircleCI::CoverageReporter to your `Gemfile` and `bundle install`:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'circleci-coverage_reporter', group: :test
|
13
|
+
```
|
14
|
+
|
15
|
+
2. Load `circleci/coverage_reporter/rake_task` in your `Rakefile`:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
require 'circleci/coverage_reporter/rake_task' if ENV['CIRCLECI']
|
19
|
+
```
|
20
|
+
|
21
|
+
3. Issue CircleCI and GitHub token and add them to build environment variables with the following nameing convention:
|
22
|
+
|
23
|
+
Name | Value
|
24
|
+
-----------------------------------|----------------------------------------------------------------
|
25
|
+
`COVERAGE_REPORTER_CIRCLECI_TOKEN` | CircleCI API token with "view-builds" scope
|
26
|
+
`COVERAGE_REPORTER_VCS_TOKEN` | GitHub personal access token with "repo" or "public_repo" scope
|
27
|
+
|
28
|
+
4. Add the following steop to your `circle.yml`:
|
29
|
+
|
30
|
+
```yaml
|
31
|
+
test:
|
32
|
+
post:
|
33
|
+
- bundle exec circleci:report_coverage
|
34
|
+
```
|
35
|
+
|
36
|
+
## Example
|
37
|
+
|
38
|
+

|
39
|
+
|
40
|
+
## Configuring CircleCI::CoverageReporter
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
CircleCI::CoverageReporter.configure do |config|
|
44
|
+
config.circleci_token = YOUR_CIRCLECI_API_TOKEN
|
45
|
+
config.vcr_token = YOUR_GITHUB_PERSONAL_ACCESS_TOKEN
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
then:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
CircleCI::CoverageReporter.run
|
53
|
+
```
|
54
|
+
|
55
|
+
## License
|
56
|
+
|
57
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'rubocop/rake_task'
|
4
|
+
require 'yard'
|
5
|
+
require 'circleci/coverage_reporter/rake_task'
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new do |task|
|
8
|
+
task.verbose = false
|
9
|
+
end
|
10
|
+
|
11
|
+
RuboCop::RakeTask.new
|
12
|
+
|
13
|
+
YARD::Rake::YardocTask.new
|
14
|
+
|
15
|
+
task default: [:spec, :rubocop]
|
data/bin/setup
ADDED
data/circle.yml
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
machine:
|
2
|
+
ruby:
|
3
|
+
version: 2.4.0
|
4
|
+
test:
|
5
|
+
override:
|
6
|
+
- bundle exec rspec --format progress --require rspec_junit_formatter --format RspecJunitFormatter -o $CIRCLE_TEST_REPORTS/rspec/junit.xml
|
7
|
+
- bundle exec rubocop
|
8
|
+
post:
|
9
|
+
- bundle exec rake circleci:report_coverage
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'circleci/coverage_reporter/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'circleci-coverage_reporter'
|
8
|
+
spec.version = CircleCI::CoverageReporter::Version.to_s
|
9
|
+
spec.authors = ['Yuku Takahashi']
|
10
|
+
spec.email = ['yuku@qiita.com']
|
11
|
+
|
12
|
+
spec.summary = 'Report test coverage to your GitHub repository'
|
13
|
+
spec.description = spec.summary
|
14
|
+
spec.homepage = 'https://github.com/increments/circleci-coverage_reporter'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/}) }
|
18
|
+
spec.bindir = 'exe'
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_dependency 'faraday', '~> 0.9'
|
23
|
+
spec.add_development_dependency 'bundler', '~> 1.12'
|
24
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'coverage_reporter/configuration'
|
2
|
+
require_relative 'coverage_reporter/client'
|
3
|
+
require_relative 'coverage_reporter/runner'
|
4
|
+
|
5
|
+
module CircleCI
|
6
|
+
module CoverageReporter
|
7
|
+
# @return [Configuration]
|
8
|
+
def self.configuration
|
9
|
+
@configuration ||= Configuration.new
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Client]
|
13
|
+
def self.client
|
14
|
+
@client ||= Client.new(configuration)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Yields the global configuration to a block.
|
18
|
+
#
|
19
|
+
# @yield [Configuration]
|
20
|
+
def self.configure
|
21
|
+
yield configuration if block_given?
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [void]
|
25
|
+
def self.run
|
26
|
+
Runner.new.run
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require_relative 'report'
|
2
|
+
|
3
|
+
module CircleCI
|
4
|
+
module CoverageReporter
|
5
|
+
# @abstract Subclass and override {#name}, {#create_build_result} and {#create_current_result}
|
6
|
+
# to implement a custom Reporter class.
|
7
|
+
class AbstractReporter
|
8
|
+
# @param base_build [Build, nil]
|
9
|
+
# @param previous_build [Build, nil]
|
10
|
+
# @return [Report]
|
11
|
+
def report(base_build, previous_build)
|
12
|
+
Report.new(
|
13
|
+
self,
|
14
|
+
current: create_current_result,
|
15
|
+
base: create_build_result(base_build),
|
16
|
+
previous: create_build_result(previous_build)
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [String]
|
21
|
+
def name
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# @param build [Build, nil]
|
28
|
+
# @return [AbstractResult, nil]
|
29
|
+
def create_build_result(build) # rubocop:disable Lint/UnusedMethodArgument
|
30
|
+
raise NotImplementedError
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [AbstractResult]
|
34
|
+
def create_current_result
|
35
|
+
raise NotImplementedError
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Client]
|
39
|
+
def client
|
40
|
+
CoverageReporter.client
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CircleCI
|
2
|
+
module CoverageReporter
|
3
|
+
# @abstract Subclass and override {#coverage} to implement a custom Result class.
|
4
|
+
class AbstractResult
|
5
|
+
# @return [Float]
|
6
|
+
def coverage
|
7
|
+
raise NotImplementedError
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [String] URL for coverage index.html
|
11
|
+
def url
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module CircleCI
|
2
|
+
module CoverageReporter
|
3
|
+
# @abstract Subclass and override {#create_comment} to implement a custom VCS client class.
|
4
|
+
class AbstractVCSClient
|
5
|
+
# @param token [String]
|
6
|
+
def initialize(token)
|
7
|
+
@token = token
|
8
|
+
end
|
9
|
+
|
10
|
+
# @param reports [Array<Report>]
|
11
|
+
# @return [void]
|
12
|
+
def create_comment(reports) # rubocop:disable Lint/UnusedMethodArgument
|
13
|
+
raise NotImplementedError
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :token
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CircleCI
|
2
|
+
module CoverageReporter
|
3
|
+
# Encapsulate a CircleCI artifact
|
4
|
+
Artifact = Struct.new(:path, :pretty_path, :node_index, :url) do
|
5
|
+
# @return [Boolean]
|
6
|
+
def end_with?(value)
|
7
|
+
pretty_path.end_with?(value)
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [String]
|
11
|
+
def body
|
12
|
+
@body ||= CoverageReporter.client.get(url).body
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module CircleCI
|
2
|
+
module CoverageReporter
|
3
|
+
# Encapsulate a CircleCI build
|
4
|
+
Build = Struct.new(:vcs_revision, :build_number) do
|
5
|
+
# @param revision [String]
|
6
|
+
# @return [Boolean]
|
7
|
+
def match?(revision)
|
8
|
+
vcs_revision.start_with?(revision)
|
9
|
+
end
|
10
|
+
|
11
|
+
# @return [Array<Artifact>]
|
12
|
+
def artifacts
|
13
|
+
@artifacts ||= CoverageReporter.client.artifacts(build_number)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
require_relative 'artifact'
|
4
|
+
require_relative 'build'
|
5
|
+
|
6
|
+
module CircleCI
|
7
|
+
module CoverageReporter
|
8
|
+
class Client
|
9
|
+
CIRCLECI_ENDPOINT = 'https://circleci.com/api/v1.1'.freeze
|
10
|
+
|
11
|
+
# @return [Configuration]
|
12
|
+
attr_reader :configuration
|
13
|
+
|
14
|
+
# @param configuration [Configuration]
|
15
|
+
def initialize(configuration)
|
16
|
+
@configuration = configuration
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param build_number [Integer, nil]
|
20
|
+
# @return [Build, nil]
|
21
|
+
def single_build(build_number)
|
22
|
+
return unless build_number
|
23
|
+
create_build(JSON.parse(get(single_build_url(build_number)).body))
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param build_number [Integer]
|
27
|
+
# @return [Array<Artifact>]
|
28
|
+
def artifacts(build_number)
|
29
|
+
JSON.parse(get(artifacts_url(build_number)).body).map do |hash|
|
30
|
+
Artifact.new(hash['path'], hash['pretty_path'], hash['node_index'], hash['url'])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param revision [String]
|
35
|
+
# @param branch [String, nil]
|
36
|
+
# @return [Integer, nil]
|
37
|
+
def build_number_by_revision(revision, branch: nil)
|
38
|
+
build = recent_builds(branch).find { |b| b.match?(revision) }
|
39
|
+
build ? build.build_number : nil
|
40
|
+
end
|
41
|
+
|
42
|
+
# @param url [String]
|
43
|
+
# @param params [Hash]
|
44
|
+
# @return [Faraday::Response]
|
45
|
+
def get(url, params = {})
|
46
|
+
params['circle-token'] = configuration.circleci_token
|
47
|
+
Faraday.get(url + '?' + params.map { |k, v| "#{k}=#{v}" }.join('&'))
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# @param build_number [Integer]
|
53
|
+
# @return [String]
|
54
|
+
def artifacts_url(build_number)
|
55
|
+
[
|
56
|
+
CIRCLECI_ENDPOINT,
|
57
|
+
'project',
|
58
|
+
configuration.vcs_type,
|
59
|
+
configuration.project,
|
60
|
+
build_number,
|
61
|
+
'artifacts'
|
62
|
+
].join('/')
|
63
|
+
end
|
64
|
+
|
65
|
+
# @param branch [String, nil]
|
66
|
+
# @return [Array<Build>]
|
67
|
+
def recent_builds(branch)
|
68
|
+
JSON.parse(get(recent_builds_url(branch), limit: 100).body).map(&method(:create_build))
|
69
|
+
end
|
70
|
+
|
71
|
+
# @param branch [String, nil]
|
72
|
+
# @return [String]
|
73
|
+
def recent_builds_url(branch)
|
74
|
+
elements = [
|
75
|
+
CIRCLECI_ENDPOINT,
|
76
|
+
'project',
|
77
|
+
configuration.vcs_type,
|
78
|
+
configuration.project
|
79
|
+
]
|
80
|
+
elements += ['tree', branch] if branch
|
81
|
+
elements.join('/')
|
82
|
+
end
|
83
|
+
|
84
|
+
# @param build_number [Integer]
|
85
|
+
# @return [String]
|
86
|
+
def single_build_url(build_number)
|
87
|
+
[
|
88
|
+
CIRCLECI_ENDPOINT,
|
89
|
+
'project',
|
90
|
+
configuration.vcs_type,
|
91
|
+
configuration.project,
|
92
|
+
build_number
|
93
|
+
].join('/')
|
94
|
+
end
|
95
|
+
|
96
|
+
# @param hash [Hash]
|
97
|
+
# @return [Build]
|
98
|
+
def create_build(hash)
|
99
|
+
Build.new(hash['vcs_revision'], hash['build_num'])
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative 'simplecov/reporter'
|
2
|
+
|
3
|
+
module CircleCI
|
4
|
+
module CoverageReporter
|
5
|
+
class Configuration
|
6
|
+
DEFAULT_REPORTERS = [SimpleCov::Reporter.new].freeze
|
7
|
+
DEFAULT_VCS_TYPE = 'github'.freeze
|
8
|
+
|
9
|
+
attr_accessor :circleci_token, :vcs_token
|
10
|
+
attr_writer :artifacts_dir, :base_revision, :current_build_number, :current_revision, :previous_build_number,
|
11
|
+
:reporters, :repository_name, :user_name, :vcs_type
|
12
|
+
|
13
|
+
# @return [String]
|
14
|
+
def project
|
15
|
+
"#{user_name}/#{repository_name}"
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [Array<AbstractReporter>]
|
19
|
+
def reporters
|
20
|
+
@reporters ||= DEFAULT_REPORTERS
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [String]
|
24
|
+
def vcs_type
|
25
|
+
@vcs_type ||= DEFAULT_VCS_TYPE
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [String]
|
29
|
+
def artifacts_dir
|
30
|
+
@artifacts_dir ||= ENV['CIRCLE_ARTIFACTS']
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [String]
|
34
|
+
def base_revision
|
35
|
+
@base_revision ||= `git merge-base origin/master HEAD`.strip
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Integer]
|
39
|
+
def current_build_number
|
40
|
+
@current_build_number ||= ENV['CIRCLE_BUILD_NUM']
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [String]
|
44
|
+
def current_revision
|
45
|
+
@current_revision ||= ENV['CIRCLE_SHA1']
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [Integer]
|
49
|
+
def previous_build_number
|
50
|
+
@previous_build_number ||= ENV['CIRCLE_PREVIOUS_BUILD_NUM'].to_i
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [String]
|
54
|
+
def repository_name
|
55
|
+
@repository_name ||= ENV['CIRCLE_PROJECT_REPONAME']
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [String]
|
59
|
+
def user_name
|
60
|
+
@user_name ||= ENV['CIRCLE_PROJECT_USERNAME']
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative 'abstract_vcs_client'
|
2
|
+
|
3
|
+
module CircleCI
|
4
|
+
module CoverageReporter
|
5
|
+
class GitHubClient < AbstractVCSClient
|
6
|
+
# @note Implement {AbstractVCSClient#create_comment}
|
7
|
+
# @param reports [Array<Report>]
|
8
|
+
# @return [void]
|
9
|
+
def create_comment(reports)
|
10
|
+
Faraday.new(url: 'https://api.github.com').post do |req|
|
11
|
+
req.url ['/repos', configuration.project, 'commits', configuration.current_revision, 'comments'].join('/')
|
12
|
+
req.headers['Authorization'] = "token #{token}"
|
13
|
+
req.headers['Content-Type'] = 'application/json'
|
14
|
+
req.body = JSON.generate(body: reports.join("\n"))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# @return [Client]
|
21
|
+
def configuration
|
22
|
+
CoverageReporter.client.configuration
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'circleci/coverage_reporter'
|
2
|
+
|
3
|
+
namespace :circleci do
|
4
|
+
desc 'Report test coverage'
|
5
|
+
task :report_coverage do
|
6
|
+
abort unless ENV['CIRCLECI']
|
7
|
+
|
8
|
+
CircleCI::CoverageReporter.configure do |config|
|
9
|
+
config.circleci_token = ENV['COVERAGE_REPORTER_CIRCLECI_TOKEN']
|
10
|
+
config.vcs_token = ENV['COVERAGE_REPORTER_VCS_TOKEN']
|
11
|
+
end
|
12
|
+
|
13
|
+
CircleCI::CoverageReporter.run
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module CircleCI
|
2
|
+
module CoverageReporter
|
3
|
+
class Report
|
4
|
+
# @param reporter [AbstractReporter]
|
5
|
+
# @param current [AbstractResult]
|
6
|
+
# @param base [AbstractResult, nil]
|
7
|
+
# @param previous [AbstractResult, nil]
|
8
|
+
def initialize(reporter, current:, base:, previous:)
|
9
|
+
@reporter = reporter
|
10
|
+
@current_result = current
|
11
|
+
@base_result = base
|
12
|
+
@previous_result = previous
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [String]
|
16
|
+
def to_s
|
17
|
+
"#{link}: #{current_result.coverage}%#{emoji}#{progress}"
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :reporter, :current_result, :base_result, :previous_result
|
23
|
+
|
24
|
+
# @return [String]
|
25
|
+
def link
|
26
|
+
"[#{reporter.name}](#{current_result.url})"
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [String, nil]
|
30
|
+
def emoji
|
31
|
+
return unless base_result
|
32
|
+
if current_result.coverage < base_result.coverage
|
33
|
+
' :chart_with_downwards_trend:'
|
34
|
+
elsif current_result.coverage > base_result.coverage
|
35
|
+
' :chart_with_upwards_trend:'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [String, nil]
|
40
|
+
def progress
|
41
|
+
elements = [base_progress, branch_progress].compact
|
42
|
+
elements.empty? ? nil : " (#{elements.join(', ')})"
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [String, nil]
|
46
|
+
def base_progress
|
47
|
+
return unless base_result
|
48
|
+
"[master](#{base_result.url}): #{diff(current_result, base_result)}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [String, nil]
|
52
|
+
def branch_progress
|
53
|
+
return unless previous_result
|
54
|
+
"[previous](#{previous_result.url}): #{diff(current_result, previous_result)}"
|
55
|
+
end
|
56
|
+
|
57
|
+
# @param after_result [AbstractResult]
|
58
|
+
# @param before_result [AbstractResult]
|
59
|
+
# @return [String]
|
60
|
+
def diff(after_result, before_result)
|
61
|
+
value = (after_result.coverage - before_result.coverage).round(2)
|
62
|
+
if value.positive?
|
63
|
+
"+#{value}"
|
64
|
+
elsif value.negative?
|
65
|
+
value.to_s
|
66
|
+
else
|
67
|
+
'±0'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require_relative 'github_client'
|
2
|
+
|
3
|
+
module CircleCI
|
4
|
+
module CoverageReporter
|
5
|
+
class Runner
|
6
|
+
# @return [void]
|
7
|
+
def run
|
8
|
+
reports = reporters.map { |reporter| reporter.report(base_build, previous_build) }
|
9
|
+
vcs_client.create_comment(reports)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
# @return [AbstractVCSClient]
|
15
|
+
def vcs_client
|
16
|
+
case client.configuration.vcs_type
|
17
|
+
when 'github'
|
18
|
+
GitHubClient.new(client.configuration.vcs_token)
|
19
|
+
else
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Build]
|
25
|
+
def base_build
|
26
|
+
@base_build ||= client.single_build(base_build_number)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Build]
|
30
|
+
def previous_build
|
31
|
+
@previous_build ||= client.single_build(previous_build_number)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Client]
|
35
|
+
def client
|
36
|
+
CoverageReporter.client
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [String, nil]
|
40
|
+
def base_revision
|
41
|
+
client.configuration.base_revision
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Integer, nil]
|
45
|
+
def previous_build_number
|
46
|
+
client.configuration.previous_build_number
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Array<AbstractReporter>]
|
50
|
+
def reporters
|
51
|
+
client.configuration.reporters
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Integer, nil]
|
55
|
+
def base_build_number
|
56
|
+
@base_build_number ||= client.build_number_by_revision(base_revision, branch: 'master')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative '../abstract_result'
|
2
|
+
|
3
|
+
module CircleCI
|
4
|
+
module CoverageReporter
|
5
|
+
module SimpleCov
|
6
|
+
class BuildResult < AbstractResult
|
7
|
+
# @param build [Build]
|
8
|
+
def initialize(build)
|
9
|
+
@build = build
|
10
|
+
end
|
11
|
+
|
12
|
+
# @note Implement {AbstractResult#coverage}
|
13
|
+
# @return [Float]
|
14
|
+
def coverage
|
15
|
+
JSON.parse(find_artifact('.last_run.json').body)['result']['covered_percent']
|
16
|
+
end
|
17
|
+
|
18
|
+
# @note Implement {AbstractResult#url}
|
19
|
+
# @return [String]
|
20
|
+
def url
|
21
|
+
find_artifact('index.html').url
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :build
|
27
|
+
|
28
|
+
# @param end_with [String]
|
29
|
+
# @return [Artifact]
|
30
|
+
def find_artifact(end_with)
|
31
|
+
build.artifacts.find { |a| a.end_with?(end_with) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative '../abstract_result'
|
2
|
+
|
3
|
+
module CircleCI
|
4
|
+
module CoverageReporter
|
5
|
+
module SimpleCov
|
6
|
+
class CurrentResult < AbstractResult
|
7
|
+
# @param path [String] path to coverage directory
|
8
|
+
def initialize(path)
|
9
|
+
@path = path
|
10
|
+
end
|
11
|
+
|
12
|
+
# @note Implement {AbstractResult#coverage}
|
13
|
+
# @return [Float]
|
14
|
+
def coverage
|
15
|
+
JSON.parse(File.read(join('.last_run.json')))['result']['covered_percent']
|
16
|
+
end
|
17
|
+
|
18
|
+
# @note Implement {AbstractResult#url}
|
19
|
+
# @return [String]
|
20
|
+
def url
|
21
|
+
[
|
22
|
+
'https://circle-artifacts.com/gh',
|
23
|
+
configuration.project,
|
24
|
+
configuration.current_build_number,
|
25
|
+
'artifacts',
|
26
|
+
"0#{configuration.artifacts_dir}",
|
27
|
+
path,
|
28
|
+
'index.html'
|
29
|
+
].join('/')
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :path
|
35
|
+
|
36
|
+
# @return
|
37
|
+
def join(name)
|
38
|
+
File.join(configuration.artifacts_dir, path, name)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Client]
|
42
|
+
def configuration
|
43
|
+
CoverageReporter.client.configuration
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
require_relative '../abstract_reporter'
|
4
|
+
require_relative 'build_result'
|
5
|
+
require_relative 'current_result'
|
6
|
+
|
7
|
+
module CircleCI
|
8
|
+
module CoverageReporter
|
9
|
+
module SimpleCov
|
10
|
+
class Reporter < AbstractReporter
|
11
|
+
DEFAULT_PATH = 'coverage'.freeze
|
12
|
+
|
13
|
+
# @param path [String] relative path from artifacts dir to coverage directory
|
14
|
+
def initialize(path = DEFAULT_PATH)
|
15
|
+
@path = path
|
16
|
+
end
|
17
|
+
|
18
|
+
# @note Implement {AbstractReporter#name}
|
19
|
+
# @return [String]
|
20
|
+
def name
|
21
|
+
'SimpleCov'
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :path
|
27
|
+
|
28
|
+
# @note Implement {AbstractReporter#create_build_result}
|
29
|
+
# @param build [Build, nil]
|
30
|
+
# @return [BuildResult, nil]
|
31
|
+
def create_build_result(build)
|
32
|
+
return unless build
|
33
|
+
BuildResult.new(build)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @note Implement {AbstractReporter#create_current_result}
|
37
|
+
# @return [CurrentResult]
|
38
|
+
def create_current_result
|
39
|
+
CurrentResult.new(path)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: circleci-coverage_reporter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yuku Takahashi
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-03-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.9'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.12'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.12'
|
41
|
+
description: Report test coverage to your GitHub repository
|
42
|
+
email:
|
43
|
+
- yuku@qiita.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- ".rspec"
|
50
|
+
- ".rubocop.yml"
|
51
|
+
- CHANGELOG.md
|
52
|
+
- Gemfile
|
53
|
+
- LICENSE.txt
|
54
|
+
- README.md
|
55
|
+
- Rakefile
|
56
|
+
- bin/setup
|
57
|
+
- circle.yml
|
58
|
+
- circleci-coverage_reporter.gemspec
|
59
|
+
- lib/circleci/coverage_reporter.rb
|
60
|
+
- lib/circleci/coverage_reporter/abstract_reporter.rb
|
61
|
+
- lib/circleci/coverage_reporter/abstract_result.rb
|
62
|
+
- lib/circleci/coverage_reporter/abstract_vcs_client.rb
|
63
|
+
- lib/circleci/coverage_reporter/artifact.rb
|
64
|
+
- lib/circleci/coverage_reporter/build.rb
|
65
|
+
- lib/circleci/coverage_reporter/client.rb
|
66
|
+
- lib/circleci/coverage_reporter/configuration.rb
|
67
|
+
- lib/circleci/coverage_reporter/github_client.rb
|
68
|
+
- lib/circleci/coverage_reporter/rake_task.rb
|
69
|
+
- lib/circleci/coverage_reporter/report.rb
|
70
|
+
- lib/circleci/coverage_reporter/runner.rb
|
71
|
+
- lib/circleci/coverage_reporter/simplecov/build_result.rb
|
72
|
+
- lib/circleci/coverage_reporter/simplecov/current_result.rb
|
73
|
+
- lib/circleci/coverage_reporter/simplecov/reporter.rb
|
74
|
+
- lib/circleci/coverage_reporter/version.rb
|
75
|
+
homepage: https://github.com/increments/circleci-coverage_reporter
|
76
|
+
licenses:
|
77
|
+
- MIT
|
78
|
+
metadata: {}
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 2.5.1
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: Report test coverage to your GitHub repository
|
99
|
+
test_files: []
|