build_eval 0.0.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/lib/build_eval/build_result.rb +39 -0
- data/lib/build_eval/build_results.rb +23 -0
- data/lib/build_eval/ci_server/decorator.rb +25 -0
- data/lib/build_eval/ci_server/team_city.rb +33 -0
- data/lib/build_eval/monitor.rb +15 -0
- data/lib/build_eval/status.rb +51 -0
- data/lib/build_eval/version.rb +3 -0
- data/lib/build_eval.rb +33 -0
- data/spec/lib/build_eval/build_result_spec.rb +85 -0
- data/spec/lib/build_eval/build_results_spec.rb +82 -0
- data/spec/lib/build_eval/ci_server/decorator_spec.rb +97 -0
- data/spec/lib/build_eval/ci_server/team_city_spec.rb +111 -0
- data/spec/lib/build_eval/monitor_spec.rb +37 -0
- data/spec/lib/build_eval/status_spec.rb +138 -0
- data/spec/lib/build_eval_spec.rb +58 -0
- data/spec/spec_helper.rb +13 -0
- metadata +167 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1daad2c7e52f801829e07f22003bf4536cf91a51
|
4
|
+
data.tar.gz: 3ab822f2da5e6cad9f5a22d5f0a9b5368d162b98
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a35303425c8ac6b5cec64f15ae1daec79c08cafa96d2193a8e75ea7f701fc05144dc898a81f232df11d8888ca440f8f55257da4d2c1d880263925cda502ff1c8
|
7
|
+
data.tar.gz: 12d7ceb5d11e619699da7cc19965e8accecd442bddae5e4b81692b8434bcbebcfe6e038972cd16075bf686d434b41862e2fd7f145bbedc65f03475b975902b05
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module BuildEval
|
2
|
+
|
3
|
+
class BuildResult
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def create(args)
|
8
|
+
self.new(build_name: args[:build_name], status: BuildEval::Status.find(args[:status_name]))
|
9
|
+
end
|
10
|
+
|
11
|
+
def unknown(build_name)
|
12
|
+
self.new(build_name: build_name, status: BuildEval::Status::UNKNOWN)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :build_name
|
18
|
+
attr_reader :status
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def initialize(args)
|
23
|
+
@build_name = args[:build_name]
|
24
|
+
@status = args[:status]
|
25
|
+
end
|
26
|
+
|
27
|
+
public
|
28
|
+
|
29
|
+
def unsuccessful?
|
30
|
+
@status.unsuccessful?
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
"#{@build_name}: #{@status}"
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module BuildEval
|
2
|
+
|
3
|
+
class BuildResults
|
4
|
+
|
5
|
+
def initialize(build_results)
|
6
|
+
@build_results = build_results
|
7
|
+
end
|
8
|
+
|
9
|
+
def status
|
10
|
+
BuildEval::Status.effective_status(@build_results.map(&:status))
|
11
|
+
end
|
12
|
+
|
13
|
+
def unsuccessful
|
14
|
+
@build_results.find_all(&:unsuccessful?)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
@build_results.map(&:to_s).join(", ")
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module BuildEval
|
2
|
+
module CIServer
|
3
|
+
|
4
|
+
class Decorator
|
5
|
+
|
6
|
+
def initialize(delegate)
|
7
|
+
@delegate = delegate
|
8
|
+
end
|
9
|
+
|
10
|
+
def build_result(name)
|
11
|
+
begin
|
12
|
+
@delegate.build_result(name)
|
13
|
+
rescue Exception
|
14
|
+
BuildEval::BuildResult.unknown(name)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def monitor(*build_names)
|
19
|
+
BuildEval::Monitor.new(server: @delegate, build_names: build_names.flatten)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module BuildEval
|
2
|
+
module CIServer
|
3
|
+
|
4
|
+
class TeamCity
|
5
|
+
|
6
|
+
def initialize(args)
|
7
|
+
@base_uri = args[:uri]
|
8
|
+
@username = args[:username]
|
9
|
+
@password = args[:password]
|
10
|
+
end
|
11
|
+
|
12
|
+
def build_result(name)
|
13
|
+
response = issue_request(name)
|
14
|
+
build_element = Nokogiri::XML(response.body).xpath("//build").first
|
15
|
+
raise "Unexpected build response: #{response.message}" unless build_element
|
16
|
+
BuildEval::BuildResult.create(build_name: name, status_name: build_element.attribute("status").value)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def issue_request(name)
|
22
|
+
uri = URI.parse("#{@base_uri}/httpAuth/app/rest/buildTypes/id:#{name}/builds")
|
23
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
|
24
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
25
|
+
request.basic_auth(@username, @password)
|
26
|
+
http.request(request)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module BuildEval
|
2
|
+
|
3
|
+
class Monitor
|
4
|
+
|
5
|
+
def initialize(args)
|
6
|
+
@server = args[:server]
|
7
|
+
@build_names = args[:build_names]
|
8
|
+
end
|
9
|
+
|
10
|
+
def evaluate
|
11
|
+
BuildEval::BuildResults.new(@build_names.map { |build_name| @server.build_result(build_name) })
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module BuildEval
|
2
|
+
|
3
|
+
class Status
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def initialize(args)
|
8
|
+
@severity = args[:severity]
|
9
|
+
@symbol = args[:symbol]
|
10
|
+
@description = args[:description]
|
11
|
+
end
|
12
|
+
|
13
|
+
public
|
14
|
+
|
15
|
+
SUCCESS = self.new(severity: 0, symbol: :success!, description: "succeeded")
|
16
|
+
FAILURE = self.new(severity: 2, symbol: :failed!, description: "failed")
|
17
|
+
UNKNOWN = self.new(severity: 1, symbol: :warning!, description: "unknown")
|
18
|
+
|
19
|
+
class << self
|
20
|
+
|
21
|
+
def find(name)
|
22
|
+
begin
|
23
|
+
self.const_get(name)
|
24
|
+
rescue NameError
|
25
|
+
raise "Build status '#{name}' is invalid"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def effective_status(statuses)
|
30
|
+
statuses.sort_by { |status| status.severity }.last
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :severity
|
36
|
+
|
37
|
+
def unsuccessful?
|
38
|
+
self != SUCCESS
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_sym
|
42
|
+
@symbol
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s
|
46
|
+
@description
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
data/lib/build_eval.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
require_relative 'build_eval/status'
|
4
|
+
require_relative 'build_eval/build_result'
|
5
|
+
require_relative 'build_eval/build_results'
|
6
|
+
require_relative 'build_eval/ci_server/decorator'
|
7
|
+
require_relative 'build_eval/ci_server/team_city'
|
8
|
+
require_relative 'build_eval/monitor'
|
9
|
+
|
10
|
+
module BuildEval
|
11
|
+
|
12
|
+
class << self
|
13
|
+
|
14
|
+
def server(args)
|
15
|
+
type_args = args.clone
|
16
|
+
server_type = type_args.delete(:type)
|
17
|
+
BuildEval::CIServer::Decorator.new(server_class_for(server_type).new(type_args))
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def server_class_for(type)
|
23
|
+
begin
|
24
|
+
BuildEval::CIServer.const_get(type.to_s)
|
25
|
+
rescue NameError
|
26
|
+
raise "Server type '#{type}' is invalid"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
describe BuildEval::BuildResult do
|
2
|
+
|
3
|
+
describe "::create" do
|
4
|
+
|
5
|
+
let(:build_name) { "Some build name" }
|
6
|
+
let(:status_name) { "SUCCESS" }
|
7
|
+
|
8
|
+
subject { described_class.create(build_name: build_name, status_name: status_name) }
|
9
|
+
|
10
|
+
it "returns a result with the provided build name" do
|
11
|
+
expect(subject.build_name).to eql(build_name)
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
it "determines the status with the provided status name" do
|
16
|
+
expect(BuildEval::Status).to receive(:find).with(status_name)
|
17
|
+
|
18
|
+
subject
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns a result with the determined status" do
|
22
|
+
status = BuildEval::Status::UNKNOWN
|
23
|
+
allow(BuildEval::Status).to receive(:find).and_return(status)
|
24
|
+
|
25
|
+
expect(subject.status).to eql(status)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "::unknown" do
|
31
|
+
|
32
|
+
let(:build_name) { "Some build name" }
|
33
|
+
|
34
|
+
subject { described_class.unknown(build_name) }
|
35
|
+
|
36
|
+
it "returns a result with the provided build name" do
|
37
|
+
expect(subject.build_name).to eql(build_name)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "returns a result with an unknown status" do
|
41
|
+
expect(subject.status).to eql(BuildEval::Status::UNKNOWN)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "#unsuccessful?" do
|
47
|
+
|
48
|
+
let(:status) { instance_double(BuildEval::Status) }
|
49
|
+
let(:build_result) { described_class.create(build_name: "some build", status_name: "some status") }
|
50
|
+
|
51
|
+
subject { build_result.unsuccessful? }
|
52
|
+
|
53
|
+
before(:example) { allow(BuildEval::Status).to receive(:find).and_return(status) }
|
54
|
+
|
55
|
+
it "delegates to the underlying status" do
|
56
|
+
allow(status).to receive(:unsuccessful?).and_return(true)
|
57
|
+
|
58
|
+
expect(subject).to be(true)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#to_s" do
|
64
|
+
|
65
|
+
let(:build_name) { "Some build name" }
|
66
|
+
let(:status_string_representation) { "SUCCESS" }
|
67
|
+
let(:status) { instance_double(BuildEval::Status, to_s: status_string_representation) }
|
68
|
+
|
69
|
+
let(:build_result) { described_class.create(build_name: build_name, status_name: "some status") }
|
70
|
+
|
71
|
+
subject { build_result.to_s }
|
72
|
+
|
73
|
+
before(:example) { allow(BuildEval::Status).to receive(:find).and_return(status) }
|
74
|
+
|
75
|
+
it "contains the name of the build" do
|
76
|
+
expect(subject).to include(build_name)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "contains the string representation of the status" do
|
80
|
+
expect(subject).to include(status_string_representation)
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
describe BuildEval::BuildResults do
|
2
|
+
|
3
|
+
let(:results) { (1..3).map { instance_double(BuildEval::BuildResult) } }
|
4
|
+
|
5
|
+
let(:build_results) { described_class.new(results) }
|
6
|
+
|
7
|
+
describe "#status" do
|
8
|
+
|
9
|
+
let(:statuses) { results.map { instance_double(BuildEval::Status) } }
|
10
|
+
|
11
|
+
subject { build_results.status }
|
12
|
+
|
13
|
+
before(:example) do
|
14
|
+
results.zip(statuses).each { |result, status| allow(result).to receive(:status).and_return(status) }
|
15
|
+
end
|
16
|
+
|
17
|
+
it "determines the effective status of the build results" do
|
18
|
+
expect(BuildEval::Status).to receive(:effective_status).with(statuses)
|
19
|
+
|
20
|
+
subject
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns the effective status" do
|
24
|
+
status = instance_double(BuildEval::Status)
|
25
|
+
allow(BuildEval::Status).to receive(:effective_status).and_return(status)
|
26
|
+
|
27
|
+
expect(subject).to be(status)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#unsuccessful" do
|
33
|
+
|
34
|
+
subject { build_results.unsuccessful }
|
35
|
+
|
36
|
+
before(:example) do
|
37
|
+
results.each do |build_result|
|
38
|
+
allow(build_result).to receive(:unsuccessful?).and_return(unsuccessful_results.include?(build_result))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "when some build results are unsuccessful" do
|
43
|
+
|
44
|
+
let(:unsuccessful_results) { [ results[0], results[2] ] }
|
45
|
+
|
46
|
+
it "returns the unsuccessful build results" do
|
47
|
+
expect(subject).to eql(unsuccessful_results)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
context "when no build results are unsuccessful" do
|
53
|
+
|
54
|
+
let(:unsuccessful_results) { [] }
|
55
|
+
|
56
|
+
it "returns an empty array" do
|
57
|
+
expect(subject).to eql([])
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#to_s" do
|
65
|
+
|
66
|
+
let(:result_string_representations) { results.each_with_index.map { |_, i| "Result description ##{i}" } }
|
67
|
+
|
68
|
+
subject { build_results.to_s }
|
69
|
+
|
70
|
+
before(:example) do
|
71
|
+
results.zip(result_string_representations).each do |build_result, string|
|
72
|
+
allow(build_result).to receive(:to_s).and_return(string)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
it "contains the string representation of each result" do
|
77
|
+
result_string_representations.each { |string| expect(subject).to include(string) }
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
describe BuildEval::CIServer::Decorator do
|
2
|
+
|
3
|
+
let(:decorated_server) { double("BuildEval::CIServer::Server") }
|
4
|
+
|
5
|
+
let(:decorator) { described_class.new(decorated_server) }
|
6
|
+
|
7
|
+
describe "#build_result" do
|
8
|
+
|
9
|
+
let(:build_name) { "some build name" }
|
10
|
+
|
11
|
+
subject { decorator.build_result(build_name) }
|
12
|
+
|
13
|
+
it "delegates to the decorated server" do
|
14
|
+
expect(decorated_server).to receive(:build_result).with(build_name)
|
15
|
+
|
16
|
+
subject
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when the decorated server returns a result" do
|
20
|
+
|
21
|
+
let(:build_result) { instance_double(BuildEval::BuildResult) }
|
22
|
+
|
23
|
+
before(:example) { allow(decorated_server).to receive(:build_result).and_return(build_result) }
|
24
|
+
|
25
|
+
it "returns the result" do
|
26
|
+
expect(subject).to eql(build_result)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
context "when the decorated server raises an error" do
|
32
|
+
|
33
|
+
before(:example) { allow(decorated_server).to receive(:build_result).and_raise("Forced error") }
|
34
|
+
|
35
|
+
it "creates an unknown result" do
|
36
|
+
expect(BuildEval::BuildResult).to receive(:unknown).with(build_name)
|
37
|
+
|
38
|
+
subject
|
39
|
+
end
|
40
|
+
|
41
|
+
it "returns the unknown result" do
|
42
|
+
unknown_build_result = instance_double(BuildEval::BuildResult)
|
43
|
+
allow(BuildEval::BuildResult).to receive(:unknown).and_return(unknown_build_result)
|
44
|
+
|
45
|
+
expect(subject).to eql(unknown_build_result)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#monitor" do
|
53
|
+
|
54
|
+
let(:build_names) { (1..3).map { |i| "build##{i}" } }
|
55
|
+
|
56
|
+
subject { decorator.monitor(*build_names) }
|
57
|
+
|
58
|
+
it "creates a monitor for the decorated server" do
|
59
|
+
expect(BuildEval::Monitor).to receive(:new).with(hash_including(server: decorated_server))
|
60
|
+
|
61
|
+
subject
|
62
|
+
end
|
63
|
+
|
64
|
+
it "returns the monitor" do
|
65
|
+
monitor = instance_double(BuildEval::Monitor)
|
66
|
+
allow(BuildEval::Monitor).to receive(:new).and_return(monitor)
|
67
|
+
|
68
|
+
expect(subject).to eql(monitor)
|
69
|
+
end
|
70
|
+
|
71
|
+
context "when an array of build names is provided" do
|
72
|
+
|
73
|
+
subject { decorator.monitor(build_names) }
|
74
|
+
|
75
|
+
it "creates a monitor for the provided build names" do
|
76
|
+
expect(BuildEval::Monitor).to receive(:new).with(hash_including(build_names: build_names))
|
77
|
+
|
78
|
+
subject
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
context "when variable argument list of build names is provided" do
|
84
|
+
|
85
|
+
subject { decorator.monitor(*build_names) }
|
86
|
+
|
87
|
+
it "creates a monitor for the provided build names" do
|
88
|
+
expect(BuildEval::Monitor).to receive(:new).with(hash_including(build_names: build_names))
|
89
|
+
|
90
|
+
subject
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
describe BuildEval::CIServer::TeamCity do
|
2
|
+
|
3
|
+
let(:scheme) { "http" }
|
4
|
+
let(:host) { "some.teamcity.server" }
|
5
|
+
let(:uri) { "#{scheme}://#{host}" }
|
6
|
+
let(:username) { "some_username" }
|
7
|
+
let(:password) { "some_password" }
|
8
|
+
|
9
|
+
let(:team_city) { described_class.new(uri: uri, username: username, password: password) }
|
10
|
+
|
11
|
+
describe "#build_result" do
|
12
|
+
|
13
|
+
let(:build_name) { "some_build_name" }
|
14
|
+
let(:last_status) { "FAILED" }
|
15
|
+
|
16
|
+
let(:expected_uri) do
|
17
|
+
"#{scheme}://#{username}:#{password}@#{host}/httpAuth/app/rest/buildTypes/id:#{build_name}/builds"
|
18
|
+
end
|
19
|
+
|
20
|
+
subject { team_city.build_result(build_name) }
|
21
|
+
|
22
|
+
before(:example) { FakeWeb.register_uri(:get, expected_uri, body: response_body, status: response_status) }
|
23
|
+
|
24
|
+
context "when the server responds with build results" do
|
25
|
+
|
26
|
+
let(:response_body) do
|
27
|
+
<<-RESPONSE
|
28
|
+
<builds count="3" href="/httpAuth/app/rest/buildTypes/#{build_name}/builds/" nextHref="/httpAuth/app/rest/buildTypes/#{build_name}/builds/?count=3&start=3">
|
29
|
+
<build id="87735" buildTypeId="#{build_name}" number="2062" status="#{last_status}" state="finished" href="/httpAuth/app/rest/builds/id:87735" webUrl="#{uri}/viewLog.html?buildId=87735&buildTypeId=#{build_name}"/>
|
30
|
+
<build id="87723" buildTypeId="#{build_name}" number="2061" status="SUCCESS" state="finished" href="/httpAuth/app/rest/builds/id:87723" webUrl="#{uri}/viewLog.html?buildId=87723&buildTypeId=#{build_name}"/>
|
31
|
+
<build id="87658" buildTypeId="#{build_name}" number="2060" status="SUCCESS" state="finished" href="/httpAuth/app/rest/builds/id:87658" webUrl="#{uri}/viewLog.html?buildId=87658&buildTypeId=#{build_name}"/>
|
32
|
+
</builds>
|
33
|
+
RESPONSE
|
34
|
+
end
|
35
|
+
let(:response_status) { [ "200", "OK" ] }
|
36
|
+
|
37
|
+
it "creates a build result containing the build name" do
|
38
|
+
expect(BuildEval::BuildResult).to receive(:create).with(hash_including(build_name: build_name))
|
39
|
+
|
40
|
+
subject
|
41
|
+
end
|
42
|
+
|
43
|
+
it "creates a build result containing the latest build status" do
|
44
|
+
expect(BuildEval::BuildResult).to receive(:create).with(hash_including(status_name: last_status))
|
45
|
+
|
46
|
+
subject
|
47
|
+
end
|
48
|
+
|
49
|
+
it "returns the created result" do
|
50
|
+
build_result = instance_double(BuildEval::BuildResult)
|
51
|
+
allow(BuildEval::BuildResult).to receive(:create).and_return(build_result)
|
52
|
+
|
53
|
+
expect(subject).to eql(build_result)
|
54
|
+
end
|
55
|
+
|
56
|
+
context "and the uri has a https scheme" do
|
57
|
+
|
58
|
+
let(:scheme) { "https" }
|
59
|
+
|
60
|
+
it "creates a build result from the response" do
|
61
|
+
expect(BuildEval::BuildResult).to receive(:create).with(hash_including(status_name: last_status))
|
62
|
+
|
63
|
+
subject
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
context "when the server authentication request fails" do
|
71
|
+
|
72
|
+
let(:response_body) { "Incorrect username or password" }
|
73
|
+
let(:response_status) { [ "401", "Unauthorized" ] }
|
74
|
+
|
75
|
+
it "raises an error" do
|
76
|
+
expect { subject }.to raise_error(/Unauthorized/)
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
context "when the build is not found" do
|
82
|
+
|
83
|
+
let(:response_body) do
|
84
|
+
<<-BODY
|
85
|
+
Error has occurred during request processing (Not Found).
|
86
|
+
Error: jetbrains.buildServer.server.rest.errors.NotFoundException: No build type nor template is found by id '#{build_name}'.
|
87
|
+
BODY
|
88
|
+
end
|
89
|
+
let(:response_status) { [ "404", "Not Found" ] }
|
90
|
+
|
91
|
+
it "raises an error" do
|
92
|
+
expect { subject }.to raise_error(/Not Found/)
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
context "when the server cannot be reached" do
|
98
|
+
|
99
|
+
let(:expected_uri) { "http://some.invalid.uri" }
|
100
|
+
let(:response_body) { nil }
|
101
|
+
let(:response_status) { nil }
|
102
|
+
|
103
|
+
it "raises an error" do
|
104
|
+
expect { subject }.to raise_error(/Not Found/)
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
describe BuildEval::Monitor do
|
2
|
+
|
3
|
+
let(:server) { double("BuildEval::CIServer") }
|
4
|
+
let(:build_names) { (1..3).map { |i| "build##{i}" } }
|
5
|
+
|
6
|
+
let(:monitor) { described_class.new(server: server, build_names: build_names) }
|
7
|
+
|
8
|
+
describe "#evaluate" do
|
9
|
+
|
10
|
+
let(:results) { build_names.map { instance_double(BuildEval::BuildResult) } }
|
11
|
+
|
12
|
+
subject { monitor.evaluate }
|
13
|
+
|
14
|
+
before(:example) { allow(server).to receive(:build_result).and_return(*results) }
|
15
|
+
|
16
|
+
it "determines build results for builds of interest" do
|
17
|
+
build_names.each { |build_name| expect(server).to receive(:build_result).with(build_name) }
|
18
|
+
|
19
|
+
subject
|
20
|
+
end
|
21
|
+
|
22
|
+
it "composes a build results object containing the results" do
|
23
|
+
expect(BuildEval::BuildResults).to receive(:new).with(results)
|
24
|
+
|
25
|
+
subject
|
26
|
+
end
|
27
|
+
|
28
|
+
it "returns the build results object" do
|
29
|
+
build_results = instance_double(BuildEval::BuildResults)
|
30
|
+
expect(BuildEval::BuildResults).to receive(:new).and_return(build_results)
|
31
|
+
|
32
|
+
expect(subject).to eql(build_results)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
describe BuildEval::Status do
|
2
|
+
|
3
|
+
describe "::find" do
|
4
|
+
|
5
|
+
subject { described_class.find(name) }
|
6
|
+
|
7
|
+
context "when the name exactly matches a status constant name" do
|
8
|
+
|
9
|
+
let(:name) { "UNKNOWN" }
|
10
|
+
|
11
|
+
it "returns the constant" do
|
12
|
+
expect(subject).to be(BuildEval::Status::UNKNOWN)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
context "when the name is completely different from a status constant name" do
|
18
|
+
|
19
|
+
let(:name) { "does_not_match" }
|
20
|
+
|
21
|
+
it "raises an error indicating the name is invalid" do
|
22
|
+
expect { subject }.to raise_error("Build status '#{name}' is invalid")
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "::effective_status" do
|
30
|
+
|
31
|
+
subject { described_class.effective_status(statuses) }
|
32
|
+
|
33
|
+
context "when a single status is provided" do
|
34
|
+
|
35
|
+
let(:statuses) { [ BuildEval::Status::UNKNOWN ] }
|
36
|
+
|
37
|
+
it "returns the status" do
|
38
|
+
expect(subject).to eql(BuildEval::Status::UNKNOWN)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
context "when the statuses are ordered in descending severity" do
|
44
|
+
|
45
|
+
let(:statuses) { [ BuildEval::Status::FAILURE, BuildEval::Status::UNKNOWN, BuildEval::Status::SUCCESS ] }
|
46
|
+
|
47
|
+
it "returns the most severe status" do
|
48
|
+
expect(subject).to eql(BuildEval::Status::FAILURE)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
context "when the statuses are ordered in ascending severity" do
|
54
|
+
|
55
|
+
let(:statuses) { [ BuildEval::Status::SUCCESS, BuildEval::Status::UNKNOWN, BuildEval::Status::FAILURE ] }
|
56
|
+
|
57
|
+
it "returns the most severe status" do
|
58
|
+
expect(subject).to eql(BuildEval::Status::FAILURE)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "#unsuccessful?" do
|
66
|
+
|
67
|
+
subject { status.unsuccessful? }
|
68
|
+
|
69
|
+
context "when the status is SUCCESS" do
|
70
|
+
|
71
|
+
let(:status) { BuildEval::Status::SUCCESS }
|
72
|
+
|
73
|
+
it "returns false" do
|
74
|
+
expect(subject).to be(false)
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
{
|
80
|
+
"FAILURE" => BuildEval::Status::FAILURE,
|
81
|
+
"UNKNOWN" => BuildEval::Status::UNKNOWN
|
82
|
+
}.each do |name, status|
|
83
|
+
|
84
|
+
context "when the status is #{name}" do
|
85
|
+
|
86
|
+
let(:status) { status }
|
87
|
+
|
88
|
+
it "returns true" do
|
89
|
+
expect(subject).to be(true)
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "#to_sym" do
|
99
|
+
|
100
|
+
subject { status.to_sym }
|
101
|
+
|
102
|
+
{ SUCCESS: :success!, FAILURE: :failed!, UNKNOWN: :warning! }.each do |name, expected_symbol|
|
103
|
+
|
104
|
+
context "when the status is #{name}" do
|
105
|
+
|
106
|
+
let(:status) { BuildEval::Status.const_get(name) }
|
107
|
+
|
108
|
+
it "returns success!" do
|
109
|
+
expect(subject).to eql(expected_symbol)
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "#to_s" do
|
119
|
+
|
120
|
+
subject { status.to_s }
|
121
|
+
|
122
|
+
{ SUCCESS: "succeeded", FAILURE: "failed", UNKNOWN: "unknown" }.each do |name, expected_string|
|
123
|
+
|
124
|
+
context "when the status is #{name}" do
|
125
|
+
|
126
|
+
let(:status) { BuildEval::Status.const_get(name) }
|
127
|
+
|
128
|
+
it "returns success!" do
|
129
|
+
expect(subject).to eql(expected_string)
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
describe BuildEval do
|
2
|
+
|
3
|
+
describe "::server" do
|
4
|
+
|
5
|
+
class BuildEval::CIServer::TestableServer
|
6
|
+
|
7
|
+
def initialize(_args)
|
8
|
+
# Intentionally blank
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:server_type) { :TestableServer }
|
14
|
+
let(:server_arguments) { { key1: "value1", key2: "value2", key3: "value3" } }
|
15
|
+
let(:args) { { type: server_type }.merge(server_arguments) }
|
16
|
+
|
17
|
+
subject { described_class.server(args) }
|
18
|
+
|
19
|
+
it "constructs an instance of the server with the provided type" do
|
20
|
+
expect(BuildEval::CIServer::TestableServer).to receive(:new)
|
21
|
+
|
22
|
+
subject
|
23
|
+
end
|
24
|
+
|
25
|
+
it "constructs the instance with additional arguments" do
|
26
|
+
expect(BuildEval::CIServer::TestableServer).to receive(:new).with(server_arguments)
|
27
|
+
|
28
|
+
subject
|
29
|
+
end
|
30
|
+
|
31
|
+
it "decorates the server with standard server behaviour" do
|
32
|
+
server = instance_double(BuildEval::CIServer::TestableServer)
|
33
|
+
allow(BuildEval::CIServer::TestableServer).to receive(:new).and_return(server)
|
34
|
+
expect(BuildEval::CIServer::Decorator).to receive(:new).with(server)
|
35
|
+
|
36
|
+
subject
|
37
|
+
end
|
38
|
+
|
39
|
+
it "returns the decorated server" do
|
40
|
+
server_decorator = instance_double(BuildEval::CIServer::Decorator)
|
41
|
+
allow(BuildEval::CIServer::Decorator).to receive(:new).and_return(server_decorator)
|
42
|
+
|
43
|
+
expect(subject).to eql(server_decorator)
|
44
|
+
end
|
45
|
+
|
46
|
+
context "when a server of the provided type is not found" do
|
47
|
+
|
48
|
+
let(:server_type) { :invalid_type }
|
49
|
+
|
50
|
+
it "raises an error indicating the server type is invalid" do
|
51
|
+
expect { subject }.to raise_error("Server type '#{server_type}' is invalid")
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: build_eval
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matthew Ueckerman
|
8
|
+
- Ryan Davis
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-08-13 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: nokogiri
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.6'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.6'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rake
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '10.4'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '10.4'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: travis-lint
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '2.0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '2.0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: metric_fu
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '4.12'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '4.12'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rspec
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '3.3'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '3.3'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: fakeweb
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '1.3'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '1.3'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: simplecov
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - "~>"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0.10'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - "~>"
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0.10'
|
112
|
+
description: Evaluates the effective status of continuous integration builds. Useful
|
113
|
+
for subsequent display on information radiators.
|
114
|
+
email: matthew.ueckerman@myob.com
|
115
|
+
executables: []
|
116
|
+
extensions: []
|
117
|
+
extra_rdoc_files: []
|
118
|
+
files:
|
119
|
+
- "./lib/build_eval.rb"
|
120
|
+
- "./lib/build_eval/build_result.rb"
|
121
|
+
- "./lib/build_eval/build_results.rb"
|
122
|
+
- "./lib/build_eval/ci_server/decorator.rb"
|
123
|
+
- "./lib/build_eval/ci_server/team_city.rb"
|
124
|
+
- "./lib/build_eval/monitor.rb"
|
125
|
+
- "./lib/build_eval/status.rb"
|
126
|
+
- "./lib/build_eval/version.rb"
|
127
|
+
- "./spec/lib/build_eval/build_result_spec.rb"
|
128
|
+
- "./spec/lib/build_eval/build_results_spec.rb"
|
129
|
+
- "./spec/lib/build_eval/ci_server/decorator_spec.rb"
|
130
|
+
- "./spec/lib/build_eval/ci_server/team_city_spec.rb"
|
131
|
+
- "./spec/lib/build_eval/monitor_spec.rb"
|
132
|
+
- "./spec/lib/build_eval/status_spec.rb"
|
133
|
+
- "./spec/lib/build_eval_spec.rb"
|
134
|
+
- "./spec/spec_helper.rb"
|
135
|
+
homepage: http://github.com/MYOB-Technology/build_eval
|
136
|
+
licenses:
|
137
|
+
- MIT
|
138
|
+
metadata: {}
|
139
|
+
post_install_message:
|
140
|
+
rdoc_options: []
|
141
|
+
require_paths:
|
142
|
+
- lib
|
143
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: 1.9.3
|
148
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
requirements: []
|
154
|
+
rubyforge_project: build_eval
|
155
|
+
rubygems_version: 2.4.6
|
156
|
+
signing_key:
|
157
|
+
specification_version: 4
|
158
|
+
summary: Evaluates the effective status of continuous integration builds
|
159
|
+
test_files:
|
160
|
+
- "./spec/lib/build_eval/build_result_spec.rb"
|
161
|
+
- "./spec/lib/build_eval/build_results_spec.rb"
|
162
|
+
- "./spec/lib/build_eval/ci_server/decorator_spec.rb"
|
163
|
+
- "./spec/lib/build_eval/ci_server/team_city_spec.rb"
|
164
|
+
- "./spec/lib/build_eval/monitor_spec.rb"
|
165
|
+
- "./spec/lib/build_eval/status_spec.rb"
|
166
|
+
- "./spec/lib/build_eval_spec.rb"
|
167
|
+
- "./spec/spec_helper.rb"
|