sonar_connector 0.8.5
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.
- 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
|