circleci_reporter 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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