circleci_reporter 1.0.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.
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../report'
4
+
5
+ module CircleCIReporter
6
+ module Reporters
7
+ # @abstract Subclass and override {.default_dir}, {.default_html_file_name},
8
+ # {.default_json_file_name} and {#parse_json} to implement a custom Reporter class.
9
+ class Base
10
+ def self.default_dir
11
+ raise NotImplementedError
12
+ end
13
+
14
+ def self.default_html_file_name
15
+ raise NotImplementedError
16
+ end
17
+
18
+ def self.default_json_file_name
19
+ raise NotImplementedError
20
+ end
21
+
22
+ def initialize(options = {})
23
+ @options = options
24
+ end
25
+
26
+ # @return [Boolean]
27
+ def active?
28
+ File.directory?(File.join(configuration.artifacts_dir, dir))
29
+ end
30
+
31
+ # @param base_build [Build, nil]
32
+ # @param previous_build [Build, nil]
33
+ # @return [Report]
34
+ def report(base_build, previous_build)
35
+ Report.new(
36
+ self,
37
+ create_current_result,
38
+ base: base_build ? create_build_result(base_build) : nil,
39
+ previous: previous_build ? create_build_result(previous_build) : nil
40
+ )
41
+ end
42
+
43
+ # Name of reporter.
44
+ #
45
+ # @return [String]
46
+ def name
47
+ self.class.name.split('::').last
48
+ end
49
+
50
+ private
51
+
52
+ # @return [String]
53
+ def dir
54
+ @options[:dir] || self.class.default_dir
55
+ end
56
+
57
+ def html_file_name
58
+ @options[:html_file_name] || self.class.default_html_file_name
59
+ end
60
+
61
+ def json_file_name
62
+ @options[:json_file_name] || self.class.default_json_file_name
63
+ end
64
+
65
+ # @param build [Build, nil]
66
+ # @return [Result, nil]
67
+ def create_build_result(build)
68
+ Result.new(build_coverage(build), build_url(build))
69
+ end
70
+
71
+ # @return [AbstractResult]
72
+ def create_current_result
73
+ Result.new(current_coverage, current_url)
74
+ end
75
+
76
+ # @return [Float]
77
+ def current_coverage
78
+ parse_json(File.read(File.join(configuration.artifacts_dir, dir, json_file_name)))
79
+ end
80
+
81
+ # @return [String]
82
+ def current_url
83
+ [
84
+ 'https://circle-artifacts.com/gh',
85
+ configuration.project,
86
+ configuration.current_build_number,
87
+ 'artifacts',
88
+ "0/#{configuration.artifacts_dir}",
89
+ dir,
90
+ html_file_name
91
+ ].join('/')
92
+ end
93
+
94
+ # @param build [Build]
95
+ # @return [Float]
96
+ def build_coverage(build)
97
+ artifact = build.find_artifact(json_file_name, node_index: 0) or return Float::NAN
98
+ parse_json(artifact.body)
99
+ end
100
+
101
+ # @param build [Build]
102
+ # @return [String]
103
+ def build_url(build)
104
+ artifact = build.find_artifact(html_file_name, node_index: 0) or return '#'
105
+ artifact.url
106
+ end
107
+
108
+ def configuration
109
+ CircleCIReporter.configuration
110
+ end
111
+
112
+ # @param json [String]
113
+ # @return [Float]
114
+ def parse_json(json)
115
+ raise NotImplementedError
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ require_relative '../result'
6
+ require_relative './base'
7
+
8
+ module CircleCIReporter
9
+ module Reporters
10
+ class Flow < Base
11
+ def self.default_dir
12
+ 'flow-coverage'
13
+ end
14
+
15
+ def self.default_html_file_name
16
+ 'index.html'
17
+ end
18
+
19
+ def self.default_json_file_name
20
+ 'flow-coverage.json'
21
+ end
22
+
23
+ private
24
+
25
+ # @param json [String]
26
+ # @return [Float]
27
+ def parse_json(json)
28
+ JSON.parse(json)['percent'].to_f
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../result'
4
+
5
+ module CircleCIReporter
6
+ module Reporters
7
+ class Link
8
+ # @attr name [String]
9
+ # @attr url [String]
10
+ # @attr base_url [String, nil]
11
+ # @attr previous_url [String, nil]
12
+ LinkReport = Struct.new(:name, :url, :base_url, :previous_url) do
13
+ # @return [String]
14
+ def to_s
15
+ links = []
16
+ links << "[master](#{base_url})" if base_url
17
+ links << "[previous](#{previous_url})" if previous_url
18
+ link = links.empty? ? nil : " (#{links.join(', ')})"
19
+ "[#{name}](#{url})#{link}"
20
+ end
21
+ end
22
+
23
+ # @param path [String]
24
+ # @param name [String]
25
+ def initialize(path:, name:)
26
+ @path = path
27
+ @name = name
28
+ end
29
+
30
+ # @note Implementation for {Base#active?}
31
+ def active?
32
+ File.file?(File.join(configuration.artifacts_dir, path))
33
+ end
34
+
35
+ # @note Override {Base#name}
36
+ attr_reader :name
37
+
38
+ # @param base_build [Build, nil]
39
+ # @param previous_build [Build, nil]
40
+ # @return [LinkReport]
41
+ def report(base_build, previous_build)
42
+ LinkReport.new(name, url, extract_artifact_url(base_build), extract_artifact_url(previous_build))
43
+ end
44
+
45
+ private
46
+
47
+ attr_reader :path
48
+
49
+ # @return [String]
50
+ def url
51
+ [
52
+ 'https://circle-artifacts.com/gh',
53
+ configuration.project,
54
+ configuration.current_build_number,
55
+ 'artifacts',
56
+ "0/#{configuration.artifacts_dir}",
57
+ path
58
+ ].join('/')
59
+ end
60
+
61
+ def configuration
62
+ CircleCIReporter.configuration
63
+ end
64
+
65
+ # @param build [Build, nil]
66
+ # @return [String, nil]
67
+ def extract_artifact_url(build)
68
+ return unless build
69
+
70
+ artifact = build.find_artifact(path)
71
+ artifact ? artifact.url : nil
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ require_relative '../result'
6
+ require_relative './base'
7
+
8
+ module CircleCIReporter
9
+ module Reporters
10
+ class RubyCritic < Base
11
+ DEFAULT_DIR = 'rubycritic'
12
+ def self.default_dir
13
+ 'rubycritic'
14
+ end
15
+
16
+ def self.default_html_file_name
17
+ 'overview.html'
18
+ end
19
+
20
+ def self.default_json_file_name
21
+ 'report.json'
22
+ end
23
+
24
+ private
25
+
26
+ # @param json [String]
27
+ # @return [Float]
28
+ def parse_json(json)
29
+ JSON.parse(json)['score'].to_f
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ require_relative '../result'
6
+ require_relative './base'
7
+
8
+ module CircleCIReporter
9
+ module Reporters
10
+ class SimpleCov < Base
11
+ def self.default_dir
12
+ 'coverage'
13
+ end
14
+
15
+ def self.default_html_file_name
16
+ 'index.html'
17
+ end
18
+
19
+ def self.default_json_file_name
20
+ '.last_run.json'
21
+ end
22
+
23
+ private
24
+
25
+ # @param json [String]
26
+ # @return [Float]
27
+ def parse_json(json)
28
+ JSON.parse(json)['result']['line'].to_f
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CircleCIReporter
4
+ # @attr coverage [Float]
5
+ # @attr url [String] URL for coverage index.html
6
+ Result = Struct.new(:coverage, :url) do
7
+ # @return [String]
8
+ def pretty_coverage
9
+ "#{coverage.round(2)}%"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'vcs/github'
4
+
5
+ module CircleCIReporter
6
+ class Runner
7
+ # @return [void]
8
+ def run
9
+ reports = reporters.map { |reporter| reporter.report(base_build, previous_build) }
10
+ vcs_client.create_comment(reports.map(&:to_s).join("\n"))
11
+ end
12
+
13
+ # @return [void]
14
+ def dump
15
+ puts <<~RUNNER
16
+ Runner | Value
17
+ ------------------|-----------------------------------------------------------------------------------
18
+ base_build | #{base_build.inspect}
19
+ base_build_number | #{base_build_number.inspect}
20
+ previous_build | #{previous_build.inspect}
21
+ RUNNER
22
+ end
23
+
24
+ private
25
+
26
+ # @return [AbstractVCSClient]
27
+ def vcs_client
28
+ case configuration.vcs_type
29
+ when 'github'
30
+ VCS::GitHub.new(configuration.vcs_token)
31
+ else
32
+ raise NotImplementedError
33
+ end
34
+ end
35
+
36
+ # @return [Build, nil]
37
+ def base_build
38
+ @base_build ||= client.single_build(base_build_number)
39
+ end
40
+
41
+ # @return [Build, nil]
42
+ def previous_build
43
+ @previous_build ||= client.single_build(previous_build_number)
44
+ end
45
+
46
+ # @return [Client]
47
+ def client
48
+ CircleCIReporter.client
49
+ end
50
+
51
+ # @return [Configuration]
52
+ def configuration
53
+ CircleCIReporter.configuration
54
+ end
55
+
56
+ # @return [String, nil]
57
+ def base_revision
58
+ configuration.base_revision
59
+ end
60
+
61
+ # @return [Integer, nil]
62
+ def previous_build_number
63
+ configuration.previous_build_number
64
+ end
65
+
66
+ # @return [Array<AbstractReporter>]
67
+ def reporters
68
+ configuration.reporters
69
+ end
70
+
71
+ # @return [Integer, nil]
72
+ def base_build_number
73
+ return if configuration.base_revision == configuration.current_revision
74
+
75
+ @base_build_number ||= client.build_number_by_revision(base_revision, branch: 'master')
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../circleci_reporter'
4
+ require_relative 'client'
5
+ require_relative 'configuration'
6
+
7
+ module CircleCIReporter
8
+ # A sandbox isolates the enclosed code into an environment that looks 'new'
9
+ # meaning globally accessed objects are reset for the duration of the sandbox.
10
+ #
11
+ # @note This module is not normally available. You must require
12
+ # `circleci_reporter/sandbox` to load it.
13
+ module Sandbox
14
+ # Execute a provided block with CircleCIReporter global objects(
15
+ # configuration, client) reset.
16
+ #
17
+ # @yield [Configuration]
18
+ # @return [void]
19
+ def self.sandboxed
20
+ orig_config = CircleCIReporter.configuration
21
+ orig_client = CircleCIReporter.client
22
+
23
+ CircleCIReporter.configuration = Configuration.new
24
+ CircleCIReporter.client = Client.new
25
+
26
+ yield CircleCIReporter.configuration
27
+ ensure
28
+ CircleCIReporter.configuration = orig_config
29
+ CircleCIReporter.client = orig_client
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CircleCIReporter
4
+ module VCS
5
+ # @abstract Subclass and override {#create_comment} to implement a custom VCS client class.
6
+ class Base
7
+ # @param token [String]
8
+ def initialize(token)
9
+ @token = token
10
+ end
11
+
12
+ # @param body [String]
13
+ # @return [void]
14
+ def create_comment(body)
15
+ raise NotImplementedError
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :token
21
+ end
22
+ end
23
+ end