sonar_connector 0.8.5
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +18 -0
- data/Rakefile +41 -0
- data/VERSION +1 -0
- data/bin/sonar-connector +69 -0
- data/config/config.example.json +82 -0
- data/lib/sonar_connector.rb +40 -0
- data/lib/sonar_connector/commands/command.rb +21 -0
- data/lib/sonar_connector/commands/commit_seppuku_command.rb +15 -0
- data/lib/sonar_connector/commands/increment_status_value_command.rb +14 -0
- data/lib/sonar_connector/commands/send_admin_email_command.rb +12 -0
- data/lib/sonar_connector/commands/update_disk_usage_command.rb +13 -0
- data/lib/sonar_connector/commands/update_status_command.rb +16 -0
- data/lib/sonar_connector/config.rb +166 -0
- data/lib/sonar_connector/connectors/base.rb +243 -0
- data/lib/sonar_connector/connectors/dummy_connector.rb +17 -0
- data/lib/sonar_connector/connectors/seppuku_connector.rb +26 -0
- data/lib/sonar_connector/consumer.rb +94 -0
- data/lib/sonar_connector/controller.rb +164 -0
- data/lib/sonar_connector/emailer.rb +16 -0
- data/lib/sonar_connector/rspec/spec_helper.rb +61 -0
- data/lib/sonar_connector/status.rb +43 -0
- data/lib/sonar_connector/utils.rb +39 -0
- data/script/console +10 -0
- data/spec/sonar_connector/commands/command_spec.rb +34 -0
- data/spec/sonar_connector/commands/commit_seppuku_command_spec.rb +25 -0
- data/spec/sonar_connector/commands/increment_status_value_command_spec.rb +25 -0
- data/spec/sonar_connector/commands/send_admin_email_command_spec.rb +14 -0
- data/spec/sonar_connector/commands/update_disk_usage_command_spec.rb +21 -0
- data/spec/sonar_connector/commands/update_status_command_spec.rb +24 -0
- data/spec/sonar_connector/config_spec.rb +93 -0
- data/spec/sonar_connector/connectors/base_spec.rb +207 -0
- data/spec/sonar_connector/connectors/dummy_connector_spec.rb +22 -0
- data/spec/sonar_connector/connectors/seppuku_connector_spec.rb +37 -0
- data/spec/sonar_connector/consumer_spec.rb +116 -0
- data/spec/sonar_connector/controller_spec.rb +46 -0
- data/spec/sonar_connector/emailer_spec.rb +36 -0
- data/spec/sonar_connector/status_spec.rb +78 -0
- data/spec/sonar_connector/utils_spec.rb +62 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +6 -0
- metadata +235 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
module Sonar
|
2
|
+
module Connector
|
3
|
+
class Emailer < ActionMailer::Base
|
4
|
+
def admin_message(connector, message)
|
5
|
+
from Sonar::Connector::CONFIG.email_settings["admin_sender"]
|
6
|
+
recipients Sonar::Connector::CONFIG.email_settings["admin_recipients"]
|
7
|
+
subject "Admin email from Sonar Connector"
|
8
|
+
content_type "text/plain"
|
9
|
+
body <<-BODY
|
10
|
+
Admin email from Sonar Connector. The connector '#{connector.name}' sent the following message:
|
11
|
+
#{message}
|
12
|
+
BODY
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec'
|
2
|
+
require 'spec/autorun'
|
3
|
+
require 'rr'
|
4
|
+
|
5
|
+
Spec::Runner.configure do |config|
|
6
|
+
config.mock_with RR::Adapters::Rspec
|
7
|
+
|
8
|
+
config.prepend_before(:each) do
|
9
|
+
|
10
|
+
# This dir gets wiped after every spec run, so please - pretty please -
|
11
|
+
# don't change it to anything that you care about.
|
12
|
+
def base_dir
|
13
|
+
"/tmp/sonar-connector/"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Note this path doesn't have to be real - it's just used to intercept calls
|
17
|
+
# to the stubbed read_json_file method on Config
|
18
|
+
def valid_config_filename
|
19
|
+
"path to a valid config file"
|
20
|
+
end
|
21
|
+
|
22
|
+
def setup_valid_config_file
|
23
|
+
@config_options = {
|
24
|
+
"log_level" => "error",
|
25
|
+
"base_dir" => base_dir,
|
26
|
+
"email_settings" => {
|
27
|
+
"admin_sender" => "noreply@example.local",
|
28
|
+
"admin_recipients" => ["admin@example.local"],
|
29
|
+
"perform_deliveries" => true,
|
30
|
+
"delivery_method" => "test",
|
31
|
+
"raise_delivery_errors" => true,
|
32
|
+
"save_emails_to_disk" => false
|
33
|
+
},
|
34
|
+
|
35
|
+
"connectors" => [
|
36
|
+
"class" => "Sonar::Connector::DummyConnector",
|
37
|
+
"name" => "dummy1",
|
38
|
+
"repeat_delay" => 10
|
39
|
+
]
|
40
|
+
}
|
41
|
+
stub(Sonar::Connector::Config).read_json_file(valid_config_filename){@config_options}
|
42
|
+
Sonar::Connector.send(:remove_const, "CONFIG") if defined?(Sonar::Connector::CONFIG)
|
43
|
+
end
|
44
|
+
|
45
|
+
# This is slightly dangerous.
|
46
|
+
FileUtils.rm_rf(base_dir) if File.directory?(base_dir)
|
47
|
+
FileUtils.mkdir_p(base_dir)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
# Creates an anonmyous throw-away class of type=parent, with an additional
|
54
|
+
# proc for defining methods on the class. Tnx @mccraigmccraig :-)
|
55
|
+
def new_anon_class(parent, name="", &proc)
|
56
|
+
klass = Class.new(parent)
|
57
|
+
mc = klass.instance_eval{ class << self ; self ; end }
|
58
|
+
mc.send(:define_method, :to_s) {name}
|
59
|
+
klass.class_eval(&proc) if proc
|
60
|
+
klass
|
61
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Sonar
|
2
|
+
module Connector
|
3
|
+
|
4
|
+
# Represents the status and statistics collected by various connectors
|
5
|
+
# and is responsible for accessing, updating and persisting the status YAML file.
|
6
|
+
class Status
|
7
|
+
|
8
|
+
def initialize(config)
|
9
|
+
@status_file = config.status_file
|
10
|
+
load_status
|
11
|
+
end
|
12
|
+
|
13
|
+
def load_status
|
14
|
+
@status = YAML.load_file(status_file) rescue {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def save_status
|
18
|
+
File.open(status_file, 'w') { |f| f << status.to_yaml }
|
19
|
+
end
|
20
|
+
|
21
|
+
def set(group, key, value)
|
22
|
+
status[group] = {} unless status[group]
|
23
|
+
status[group][key] = value
|
24
|
+
status[group]['last_updated'] = Time.now.to_s
|
25
|
+
save_status
|
26
|
+
end
|
27
|
+
|
28
|
+
def [](group)
|
29
|
+
status[group]
|
30
|
+
end
|
31
|
+
|
32
|
+
def []=(group, hash)
|
33
|
+
status[group] = hash
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_accessor :status_file
|
39
|
+
attr_accessor :status
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Sonar
|
2
|
+
module Connector
|
3
|
+
module Utils
|
4
|
+
|
5
|
+
#
|
6
|
+
# Disk usage utility. Returns amount of disk space
|
7
|
+
# used in a given folder, in bytes.
|
8
|
+
def du(dir)
|
9
|
+
raise "#{dir} is not a directory" unless File.directory?(dir)
|
10
|
+
glob = File.join(dir, "**", "*")
|
11
|
+
Dir[glob].map {|f|
|
12
|
+
File.read(f).size rescue nil
|
13
|
+
}.compact.sum
|
14
|
+
end
|
15
|
+
|
16
|
+
module_function :du
|
17
|
+
|
18
|
+
def stdout_logger(base_config)
|
19
|
+
log = Logger.new STDOUT
|
20
|
+
log.level = base_config.log_level
|
21
|
+
log.formatter = Logger::Formatter.new
|
22
|
+
log.datetime_format = "%Y-%m-%d %H:%M:%S"
|
23
|
+
log
|
24
|
+
end
|
25
|
+
|
26
|
+
module_function :stdout_logger
|
27
|
+
|
28
|
+
def disk_logger(filename, base_config)
|
29
|
+
log = Logger.new filename, base_config.log_files_to_keep, base_config.log_file_max_size
|
30
|
+
log.level = base_config.log_level
|
31
|
+
log.formatter = Logger::Formatter.new
|
32
|
+
log.datetime_format = "%Y-%m-%d %H:%M:%S"
|
33
|
+
log
|
34
|
+
end
|
35
|
+
|
36
|
+
module_function :disk_logger
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/script/console
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Sonar::Connector::Command do
|
4
|
+
before do
|
5
|
+
@command
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "initialize" do
|
9
|
+
it "should set proc" do
|
10
|
+
proc = Proc.new {}
|
11
|
+
c = Sonar::Connector::Command.new(proc)
|
12
|
+
c.proc.should == proc
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "execute" do
|
17
|
+
it "should be run in context" do
|
18
|
+
|
19
|
+
# create a context instance with a #do_it method
|
20
|
+
context = new_anon_class(Object, "MyContext"){
|
21
|
+
def do_it(instance)
|
22
|
+
end
|
23
|
+
}.new
|
24
|
+
|
25
|
+
# the proc to run calls #do_it on self, and passes in self, which should be one and the same
|
26
|
+
proc = Proc.new { do_it(self) }
|
27
|
+
|
28
|
+
# ensure that do_it gets called, and the self instance passed in is the context.
|
29
|
+
mock(context).do_it(context)
|
30
|
+
|
31
|
+
Sonar::Connector::Command.new(proc).execute(context)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Sonar::Connector::CommitSeppukuCommand do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@connector = Object.new
|
7
|
+
@controller = Object.new
|
8
|
+
stub(@connector).name{"name"}
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should update the disk usage statistic" do
|
12
|
+
@command = Sonar::Connector::CommitSeppukuCommand.new
|
13
|
+
|
14
|
+
@context = Object.new
|
15
|
+
@status = Object.new
|
16
|
+
@shutdown_lambda = Object.new
|
17
|
+
|
18
|
+
mock(@context).controller{@controller}
|
19
|
+
mock(@controller).shutdown_lambda{@shutdown_lambda}
|
20
|
+
mock(@shutdown_lambda).call
|
21
|
+
|
22
|
+
@command.execute(@context)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Sonar::Connector::IncrementStatusValueCommand do
|
4
|
+
before do
|
5
|
+
setup_valid_config_file
|
6
|
+
@base_config = Sonar::Connector::Config.load valid_config_filename
|
7
|
+
@status = Sonar::Connector::Status.new @base_config
|
8
|
+
stub(@connector = Object.new).name{"name"}
|
9
|
+
stub(@context).status{@status}
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
it "should set value if it is nil" do
|
14
|
+
@status.set("name", "foo", nil)
|
15
|
+
Sonar::Connector::IncrementStatusValueCommand.new(@connector, "foo").execute(@context)
|
16
|
+
@status["name"]["foo"].should == 1
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should increment a value" do
|
20
|
+
@status.set "name", "foo", 1
|
21
|
+
Sonar::Connector::IncrementStatusValueCommand.new(@connector, "foo").execute(@context)
|
22
|
+
@status["name"]["foo"].should == 2
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Sonar::Connector::SendAdminEmailCommand do
|
4
|
+
|
5
|
+
it "should send admin email" do
|
6
|
+
@connector = Object.new
|
7
|
+
@command = Sonar::Connector::SendAdminEmailCommand.new(@connector, "message")
|
8
|
+
|
9
|
+
mock(Sonar::Connector::Emailer).deliver_admin_message(@connector, "message")
|
10
|
+
|
11
|
+
@command.execute
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Sonar::Connector::UpdateDiskUsageCommand do
|
4
|
+
|
5
|
+
it "should update the disk usage statistic" do
|
6
|
+
@connector = Object.new
|
7
|
+
mock(@connector).connector_dir{"dir"}
|
8
|
+
mock(@connector).name{"name"}
|
9
|
+
|
10
|
+
mock(Sonar::Connector::Utils).du("dir"){2048}
|
11
|
+
@command = Sonar::Connector::UpdateDiskUsageCommand.new(@connector)
|
12
|
+
|
13
|
+
@context = Object.new
|
14
|
+
@status = Object.new
|
15
|
+
mock(@status).set("name", "disk_usage", "2 Kb")
|
16
|
+
mock(@context).status{@status}
|
17
|
+
|
18
|
+
@command.execute(@context)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Sonar::Connector::UpdateStatusCommand do
|
4
|
+
|
5
|
+
it "should define constants" do
|
6
|
+
Sonar::Connector::ACTION_OK
|
7
|
+
Sonar::Connector::ACTION_FAILED
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should update the disk usage statistic" do
|
11
|
+
@connector = Object.new
|
12
|
+
mock(@connector).name{"name"}
|
13
|
+
|
14
|
+
@command = Sonar::Connector::UpdateStatusCommand.new(@connector, "last_operation", Sonar::Connector::ACTION_OK)
|
15
|
+
|
16
|
+
@context = Object.new
|
17
|
+
@status = Object.new
|
18
|
+
mock(@status).set("name", "last_operation", Sonar::Connector::ACTION_OK)
|
19
|
+
mock(@context).status{@status}
|
20
|
+
|
21
|
+
@command.execute(@context)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Sonar::Connector::Config do
|
4
|
+
|
5
|
+
before do
|
6
|
+
setup_valid_config_file
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "self.load" do
|
10
|
+
before do
|
11
|
+
@config = Sonar::Connector::Config.load(valid_config_filename)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should return config" do
|
15
|
+
@config.should be_instance_of(Sonar::Connector::Config)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should set CONFIG constant" do
|
19
|
+
Sonar::Connector::CONFIG.should == @config
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "parse" do
|
25
|
+
before do
|
26
|
+
@config = Sonar::Connector::Config.new(valid_config_filename).parse
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should return the config instance" do
|
30
|
+
@config.should be_instance_of(Sonar::Connector::Config)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should symbolize log_level" do
|
34
|
+
@config_options["log_level"] = "error"
|
35
|
+
@config = Sonar::Connector::Config.new(valid_config_filename).parse
|
36
|
+
@config.log_level.should == Logger::ERROR
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should set email settings" do
|
40
|
+
@config.email_settings.should be_instance_of(Hash)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "associate_connector_dependencies!" do
|
45
|
+
before do
|
46
|
+
@config = Sonar::Connector::Config.load(valid_config_filename)
|
47
|
+
@connector_klass = new_anon_class(Sonar::Connector::Base, "MyConnector")
|
48
|
+
end
|
49
|
+
|
50
|
+
def connector_with_name_and_source(name, source_name)
|
51
|
+
@connector_klass.new({'class'=>'MyConnector', 'name'=>name, 'source_connectors'=>[source_name], 'repeat_delay'=> 10}, @config)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should associate a source connector" do
|
55
|
+
connector1 = connector_with_name_and_source 'c1', nil
|
56
|
+
connector2 = connector_with_name_and_source 'c2', 'c1'
|
57
|
+
|
58
|
+
@config.send :associate_connector_dependencies!, [connector1, connector2]
|
59
|
+
|
60
|
+
connector1.source_connectors.should be_nil
|
61
|
+
connector2.source_connectors.should == [connector1]
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should associate multiple source connectors correctly" do
|
65
|
+
connector1 = connector_with_name_and_source 'c1', nil
|
66
|
+
connector2 = connector_with_name_and_source 'c2', 'c1'
|
67
|
+
connector3 = connector_with_name_and_source 'c3', nil
|
68
|
+
connector4 = connector_with_name_and_source 'c4', 'c3'
|
69
|
+
|
70
|
+
@config.send :associate_connector_dependencies!, [connector1, connector2, connector3, connector4]
|
71
|
+
|
72
|
+
connector1.source_connectors.should be_nil
|
73
|
+
connector2.source_connectors.should == [connector1]
|
74
|
+
connector3.source_connectors.should be_nil
|
75
|
+
connector4.source_connectors.should == [connector3]
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should raise error when source_connector doesn't exist" do
|
79
|
+
connector1 = connector_with_name_and_source 'c1', 'invalid_connector_name'
|
80
|
+
lambda{
|
81
|
+
@config.send :associate_connector_dependencies!, [connector1]
|
82
|
+
}.should raise_error(Sonar::Connector::InvalidConfig, /no such connector name is defined/)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should raise error if connector is set as its own source" do
|
86
|
+
connector1 = connector_with_name_and_source 'c1', 'c1'
|
87
|
+
lambda{
|
88
|
+
@config.send :associate_connector_dependencies!, [connector1]
|
89
|
+
}.should raise_error(Sonar::Connector::InvalidConfig, /cannot have itself as a/)
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Sonar::Connector::Base do
|
4
|
+
before do
|
5
|
+
setup_valid_config_file
|
6
|
+
@base_config = Sonar::Connector::Config.load(valid_config_filename)
|
7
|
+
@connector_klass = new_anon_class(Sonar::Connector::Base, "MyConnector") {}
|
8
|
+
@config = {'class'=>'MyConnector', 'name'=>'foo', 'repeat_delay'=> 10}
|
9
|
+
@connector = @connector_klass.new(@config, @base_config)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "initialize" do
|
13
|
+
it "should set name" do
|
14
|
+
c = @connector_klass.new(@config, @base_config)
|
15
|
+
c.name.should == 'foo'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should require repeat delay of at least 1.0 second" do
|
19
|
+
@config['repeat_delay'] = nil
|
20
|
+
lambda{
|
21
|
+
@connector_klass.new(@config, @base_config)
|
22
|
+
}.should raise_error(Sonar::Connector::InvalidConfig)
|
23
|
+
|
24
|
+
@config['repeat_delay'] = 0
|
25
|
+
lambda{
|
26
|
+
@connector_klass.new(@config, @base_config)
|
27
|
+
}.should raise_error(Sonar::Connector::InvalidConfig)
|
28
|
+
|
29
|
+
@config['repeat_delay'] = 0.9
|
30
|
+
lambda{
|
31
|
+
@connector_klass.new(@config, @base_config)
|
32
|
+
}.should raise_error(Sonar::Connector::InvalidConfig)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should set blank state hash" do
|
36
|
+
c = @connector_klass.new(@config, @base_config)
|
37
|
+
c.state.should == {}
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should parse config" do
|
41
|
+
mock.instance_of(@connector_klass).parse(@config){}
|
42
|
+
@connector_klass.new(@config, @base_config)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should load state after parsing config so as not to overwrite any state" do
|
46
|
+
@connector_klass = new_anon_class(Sonar::Connector::Base, "MyConnector"){
|
47
|
+
def parse(config)
|
48
|
+
state[:foo] = 'default state value from config'
|
49
|
+
end
|
50
|
+
}
|
51
|
+
|
52
|
+
mock.instance_of(@connector_klass).read_state(){
|
53
|
+
{:foo => 'overridden by state'}
|
54
|
+
}
|
55
|
+
|
56
|
+
c = @connector_klass.new(@config, @base_config)
|
57
|
+
c.state[:foo].should == 'overridden by state'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "read_state" do
|
62
|
+
it "should return empty hash if the file doesn't exist" do
|
63
|
+
File.exists?(@connector.send :state_file).should be_false
|
64
|
+
@connector.read_state.should == {}
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should load hash from yaml file" do
|
68
|
+
mock(File).exist?(@connector.send :state_file){true}
|
69
|
+
mock(YAML).load_file(@connector.send :state_file) { {:foo=>:bar} }
|
70
|
+
@connector.read_state.should == {:foo=>:bar}
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should log error and return empty hash if the yaml read throws error" do
|
74
|
+
mock(File).exist?(@connector.send :state_file){true}
|
75
|
+
mock(YAML).load_file(@connector.send :state_file) { raise "foo" }
|
76
|
+
mock(@connector.log).error(anything) do |param|
|
77
|
+
param.should match(/error loading/)
|
78
|
+
end
|
79
|
+
@connector.read_state.should == {}
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should return an empty-hash if the yaml read returns a non-hash" do
|
83
|
+
mock(File).exist?(@connector.send :state_file){true}
|
84
|
+
mock(YAML).load_file(@connector.send :state_file) { false }
|
85
|
+
mock(@connector.log).error(anything) do |param|
|
86
|
+
param.should match(/error loading/)
|
87
|
+
end
|
88
|
+
@connector.read_state.should == {}
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "load_state" do
|
94
|
+
it "should merge keys" do
|
95
|
+
@connector.state[:foo] = :bar
|
96
|
+
@connector.state[:baz] = 'old value'
|
97
|
+
|
98
|
+
@connector.save_state
|
99
|
+
|
100
|
+
@connector.state[:baz] = 'new value'
|
101
|
+
|
102
|
+
@connector.load_state
|
103
|
+
|
104
|
+
@connector.state[:foo].should == :bar
|
105
|
+
@connector.state[:baz] = 'old value'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "save_state" do
|
110
|
+
it "should save state to yaml" do
|
111
|
+
@connector.state[:foo] = :bar
|
112
|
+
@connector.save_state
|
113
|
+
@connector.state[:foo] = nil
|
114
|
+
@connector.load_state
|
115
|
+
@connector.state[:foo].should == :bar
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "start" do
|
120
|
+
before do
|
121
|
+
@connector = new_anon_class(Sonar::Connector::Base, "MyConnector"){
|
122
|
+
def action
|
123
|
+
true
|
124
|
+
end
|
125
|
+
}.new(@config, @base_config)
|
126
|
+
@queue = Queue.new
|
127
|
+
stub(@connector).sleep_for(anything)
|
128
|
+
|
129
|
+
# don't switch to log file, but don't close stdout either
|
130
|
+
mock(@connector).switch_to_log_file
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should peform the action once per iteration" do
|
134
|
+
mock(@connector) do
|
135
|
+
run(){true}
|
136
|
+
run(){true}
|
137
|
+
run(){false}
|
138
|
+
end
|
139
|
+
|
140
|
+
mock(@connector).action.times(2)
|
141
|
+
|
142
|
+
@connector.prepare(@queue)
|
143
|
+
@connector.start()
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should catch uncaught exceptions from the action" do
|
147
|
+
mock(@connector) do
|
148
|
+
run(){true}
|
149
|
+
run(){true}
|
150
|
+
run(){false}
|
151
|
+
end
|
152
|
+
|
153
|
+
mock(@connector) do
|
154
|
+
action(){raise "uncaught exception"}
|
155
|
+
action(){true}
|
156
|
+
end
|
157
|
+
|
158
|
+
@connector.prepare(@queue)
|
159
|
+
@connector.start
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should terminate on ThreadTerminator exception" do
|
163
|
+
stub(@connector).run{true}
|
164
|
+
mock(@connector).action(){raise Sonar::Connector::ThreadTerminator.new}
|
165
|
+
@connector.prepare(@queue)
|
166
|
+
@connector.start
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should queue status update and disk usage commands on successful action" do
|
170
|
+
mock(@connector) do
|
171
|
+
run(){true}
|
172
|
+
run(){true}
|
173
|
+
run(){false}
|
174
|
+
end
|
175
|
+
|
176
|
+
stub(Sonar::Connector::UpdateStatusCommand).new
|
177
|
+
stub(Sonar::Connector::UpdateDiskUsageCommand).new
|
178
|
+
stub(@queue, :<<)
|
179
|
+
|
180
|
+
@connector.prepare(@queue)
|
181
|
+
@connector.start
|
182
|
+
|
183
|
+
Sonar::Connector::UpdateStatusCommand.should have_received.new(anything, 'last_action', Sonar::Connector::ACTION_OK).times(2)
|
184
|
+
Sonar::Connector::UpdateDiskUsageCommand.should have_received.new(anything).times(2)
|
185
|
+
@queue.should have_received(:<<).with(anything).times(10) # 5x commands queued on 2x action invocations
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should queue error status update on unhandled action error" do
|
189
|
+
mock(@connector) do
|
190
|
+
run(){true}
|
191
|
+
run(){false}
|
192
|
+
end
|
193
|
+
|
194
|
+
mock(@connector) do
|
195
|
+
action(){raise "uncaught exception"}
|
196
|
+
end
|
197
|
+
|
198
|
+
command = Object.new
|
199
|
+
mock(Sonar::Connector::UpdateStatusCommand).new(anything, 'last_action', Sonar::Connector::ACTION_FAILED).times(1){command}
|
200
|
+
mock(@queue, :<<).with(command).times(1)
|
201
|
+
@connector.prepare(@queue)
|
202
|
+
@connector.start
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|