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