resque-cluster 0.1.1.1 → 0.2.0

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.
Files changed (37) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +3 -1
  3. data/Gemfile.lock +47 -44
  4. data/lib/resque/cluster.rb +1 -0
  5. data/lib/resque/cluster/config.rb +169 -0
  6. data/lib/resque/cluster/config/file.rb +40 -0
  7. data/lib/resque/cluster/config/verifier.rb +27 -0
  8. data/lib/resque/cluster/member.rb +17 -27
  9. data/lib/resque/cluster/version.rb +1 -1
  10. data/lib/resque/pool/patches.rb +5 -0
  11. data/resque-cluster.gemspec +1 -1
  12. data/spec/integration/cluster_spec.rb +41 -11
  13. data/spec/integration/config/bad_global_config.yml +3 -0
  14. data/spec/integration/config/global_rebalance_config3.yml +5 -0
  15. data/spec/integration/config/rebalance_config.yml +4 -0
  16. data/spec/integration/spec_helper.rb +3 -0
  17. data/spec/integration/test_member_manager.rb +50 -19
  18. data/spec/unit/config/verifier_spec.rb +86 -0
  19. data/spec/unit/config_spec.rb +194 -0
  20. data/spec/unit/member_spec.rb +5 -32
  21. data/spec/unit/resque_pool_patches_spec.rb +3 -3
  22. data/spec/unit/spec_helper.rb +5 -1
  23. data/spec/unit/support/broken_yaml_config.yml +1 -0
  24. data/spec/unit/support/empty_config.yml +1 -0
  25. data/spec/unit/support/missing_global_maximum.yml +3 -0
  26. data/spec/unit/support/missing_local_maximum.yml +3 -0
  27. data/spec/unit/support/non_hash_config.yml +0 -0
  28. data/spec/unit/support/separate_missing_global.yml +2 -0
  29. data/spec/unit/support/separate_missing_local.yml +2 -0
  30. data/spec/unit/support/single_old_missing_global.yml +9 -0
  31. data/spec/unit/support/single_old_missing_local.yml +9 -0
  32. data/spec/unit/support/valid_config.yml +13 -0
  33. data/spec/unit/support/valid_global_config.yml +1 -0
  34. data/spec/unit/support/valid_local_config.yml +1 -0
  35. data/spec/unit/support/valid_single_new_config.yml +13 -0
  36. data/spec/unit/support/valid_single_old_config.yml +10 -0
  37. metadata +72 -30
@@ -1,5 +1,5 @@
1
1
  module Resque
2
2
  class Cluster
3
- VERSION = '0.1.1.1'
3
+ VERSION = '0.2.0'
4
4
  end
5
5
  end
@@ -22,6 +22,11 @@ module Resque
22
22
  original_maintain_worker_count.bind(self).call
23
23
  end
24
24
 
25
+ def quit
26
+ log "Quiting ..."
27
+ Process.kill(:TERM, Process.pid)
28
+ end
29
+
25
30
  def cluster_update
26
31
  Resque::Cluster.member.perform if Resque::Cluster.member
27
32
  end
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.require_paths = ["lib"]
20
20
 
21
21
  s.add_dependency 'resque-pool', '~> 0.5.0'
22
- s.add_dependency 'gru', '0.1.1'
22
+ s.add_dependency 'gru', '0.1.3'
23
23
 
24
24
  s.add_development_dependency 'pry', '> 0.0'
25
25
  s.add_development_dependency 'awesome_print', '> 0.0'
@@ -2,9 +2,13 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
3
  LOCAL_CONFIG = "spec/integration/config/local_config.yml"
4
4
  GLOBAL_CONFIG = "spec/integration/config/global_config.yml"
5
+ BAD_GLOBAL_CONFIG = "spec/integration/config/bad_global_config.yml"
6
+ BAD_LOCAL_CONFIG = "spec/integration/config/bad_local_config.yml"
5
7
  LOCAL_CONFIG2 = "spec/integration/config/local_config2.yml"
6
8
  GLOBAL_CONFIG2 = "spec/integration/config/global_config2.yml"
7
9
  GLOBAL_REBALANCE_CONFIG2 = "spec/integration/config/global_rebalance_config2.yml"
10
+ GLOBAL_REBALANCE_CONFIG3 = "spec/integration/config/global_rebalance_config3.yml"
11
+ REBALANCE_CONFIG = "spec/integration/config/rebalance_config.yml"
8
12
  GLOBAL_CONFIG3 = "spec/integration/config/global_config3.yml"
9
13
  GLOBAL_CONFIG4 = "spec/integration/config/global_config4.yml"
10
14
 
@@ -49,6 +53,32 @@ RSpec.describe "Resque test-cluster" do
49
53
 
50
54
  end
51
55
 
56
+ context "Cluster with bad configs" do
57
+ before :all do
58
+ @d = TestMemberManager.new(LOCAL_CONFIG2, BAD_GLOBAL_CONFIG)
59
+ @e = TestMemberManager.new(BAD_LOCAL_CONFIG, GLOBAL_REBALANCE_CONFIG2)
60
+ @f = TestMemberManager.new("", "")
61
+ @d.start
62
+ @e.start
63
+ @f.start
64
+ sleep(5) # rebalance time
65
+ end
66
+
67
+ it 'expects counts to be correct after workers get spun up' do
68
+ expect(TestMemberManager.counts).to eq({})
69
+ expect(@d.is_running?).to eq(false)
70
+ expect(@e.is_running?).to eq(false)
71
+ expect(@f.is_running?).to eq(false)
72
+ expect(TestMemberManager.resque_cluster_members.count).to eq(0)
73
+ end
74
+
75
+ after :all do
76
+ TestMemberManager.stop_all
77
+ end
78
+
79
+ end
80
+
81
+
52
82
  context "Cluster with Rebalancing" do
53
83
  before :all do
54
84
  @d = TestMemberManager.new(LOCAL_CONFIG2, GLOBAL_REBALANCE_CONFIG2)
@@ -106,29 +136,29 @@ RSpec.describe "Resque test-cluster" do
106
136
  end
107
137
  end
108
138
 
109
- context "Multiple Configs in the same cluster" do
139
+ context "Rebalance after global maximums change" do
110
140
  before :all do
111
141
  @a = TestMemberManager.new(LOCAL_CONFIG, GLOBAL_CONFIG)
112
- @b = TestMemberManager.new(LOCAL_CONFIG2, GLOBAL_CONFIG)
113
- @c = TestMemberManager.new(LOCAL_CONFIG, GLOBAL_REBALANCE_CONFIG2)
142
+ @b = TestMemberManager.new(LOCAL_CONFIG, GLOBAL_CONFIG)
143
+ @c = TestMemberManager.new(LOCAL_CONFIG, GLOBAL_REBALANCE_CONFIG3)
114
144
  @a.start
115
145
  @b.start
116
146
  sleep(3) # rebalance time
117
147
  end
118
148
 
119
- it 'expects to have each cluster member only running workers in it\'s config' do
120
- expect(TestMemberManager.counts).to eq({"tar"=>3, "par"=>1, "par,tar,var"=>1})
121
- expect(@a.counts).to eq({"tar"=>3, "par"=>1, "par,tar,var"=>1})
122
- expect(@b.counts).to be_empty
149
+ it 'expects to have each cluster member only running workers in its config' do
150
+ expect(TestMemberManager.counts).to eq('par' => 2, 'tar' => 6, 'par,tar,var' => 1)
151
+ expect(@a.counts).to eq('par' => 1, 'tar' => 3, 'par,tar,var' => 1)
152
+ expect(@b.counts).to eq('par' => 1, 'tar' => 3)
123
153
  end
124
154
 
125
155
  it 'expects the cluster to redistribute correctly after global config change' do
126
156
  @c.start
127
157
  sleep(8) # rebalance time
128
- expect(TestMemberManager.counts).to eq({"star"=>4})
129
- expect(@a.counts).to be_empty
130
- expect(@b.counts).to eq({"star"=>4})
131
- expect(@c.counts).to be_empty
158
+ expect(TestMemberManager.counts).to eq('par' => 3, 'tar' => 6, 'par,tar,var' => 2)
159
+ expect(@a.counts).to eq('par' => 1, 'tar' => 2, 'par,tar,var' => 1)
160
+ expect(@b.counts).to eq('par' => 1, 'tar' => 2)
161
+ expect(@c.counts).to eq('par' => 1, 'tar' => 2, 'par,tar,var' => 1)
132
162
  end
133
163
 
134
164
  after :all do
@@ -0,0 +1,3 @@
1
+ par= 2
2
+ ? tar: 8
3
+ "par,tar,var"+ 1
@@ -0,0 +1,5 @@
1
+ rebalance_cluster: true
2
+ global_maximums:
3
+ par: 4
4
+ tar: 6
5
+ "par,tar,var": 2
@@ -0,0 +1,4 @@
1
+ par: 4
2
+ tar: 4
3
+ "par,tar,var": 2
4
+ star: 8
@@ -1,3 +1,6 @@
1
1
  require 'pry'
2
2
  require 'rspec'
3
+ require 'sys/proctable'
4
+ require 'resque'
5
+
3
6
  require File.expand_path(File.dirname(__FILE__) + '/test_member_manager')
@@ -1,4 +1,7 @@
1
1
  class TestMemberManager
2
+ include Sys
3
+
4
+ attr_accessor :pid
2
5
 
3
6
  def initialize(local_config_path, global_config_path, cluster_name = "test-cluster", environment = "test")
4
7
  @local_config_path = local_config_path
@@ -12,53 +15,71 @@ class TestMemberManager
12
15
  def start
13
16
  ENV['GRU_HOSTNAME'] = hostname
14
17
  @pid = spawn("bundle exec spec/integration/bin/resque-cluster_member_test -c #{@local_config_path} -E #{@environment}#{@cluster_name.nil? ? "" : " -C "+@cluster_name} -G #{@global_config_path}")
18
+ count = 0
15
19
 
16
- while ( @pool_master_pid.nil? ) do
20
+ while ( @pool_master_pid.nil? && count <= 100 ) do
17
21
  sleep(0.1)
18
- child_process = @pid #`pgrep -P #{@pid}`.strip
19
- pool = `ps -p #{child_process} -hf | grep 'resque-pool-master\\[resque-cluster\\]: managing \\[' | awk '{print $1}'`.strip.to_i
20
- @pool_master_pid = pool > 0 ? pool : nil
22
+
23
+ if (ProcTable.ps(@pid) &&
24
+ ProcTable.ps(@pid).cmdline =~ /resque-pool-master\[resque-cluster\]:\smanaging\s\[/)
25
+ pool_pid = ProcTable.ps(@pid).pid
26
+ end
27
+
28
+ @pool_master_pid = pool_pid ? pool_pid : nil
29
+ count += 1
21
30
  end
22
- puts "Pool Master pid is ---------- #{@pool_master_pid}"
31
+
32
+ puts "Pool Master pid is ---------- #{@pool_master_pid}" if @pool_master_pid
23
33
  end
24
34
 
25
35
  def stop
26
36
  puts "************************************************ About to kill : Pool Master pid ---------- #{@pool_master_pid}"
27
- Process.kill(:TERM, @pool_master_pid)
37
+ Process.kill(:QUIT, @pool_master_pid)
28
38
  while ( @pool_master_pid ) do
29
- pool = `ps -p #{@pool_master_pid} -hf | grep 'resque-pool-master\\[resque-cluster\\]: managing \\[' | awk '{print $1}'`.strip.to_i
30
- @pool_master_pid = pool > 0 ? pool : nil
39
+ if (ProcTable.ps(@pool_master_pid) &&
40
+ ! (ProcTable.ps(@pool_master_pid).cmdline =~ /resque-pool-master\[resque-cluster\]:\smanaging\s\[/))
41
+ @pool_master_pid = nil
42
+ end
31
43
  end
32
44
  @pid = nil
33
- sleep(5)
45
+ sleep(3)
46
+ end
47
+
48
+ def is_running?
49
+ (ProcTable.ps(@pid).instance_of? (Struct::ProcTableStruct)) &&
50
+ ! (ProcTable.ps(@pid).cmdline =~ /resque-pool-master\[resque-cluster\]:\smanaging\s\[/).nil?
34
51
  end
35
52
 
36
53
  def kill
37
54
  puts "************************************************ About to kill -9 : Pool Master pid ---------- #{@pool_master_pid}"
38
- `kill -9 #{@pool_master_pid}`
55
+ Process.kill(:TERM, @pool_master_pid)
39
56
  while ( @pool_master_pid ) do
40
- pool = `ps -p #{@pool_master_pid} -hf | grep 'resque-pool-master\\[resque-cluster\\]: managing \\[' | awk '{print $1}'`.strip.to_i
41
- @pool_master_pid = pool > 0 ? pool : nil
57
+ if (ProcTable.ps(@pool_master_pid) &&
58
+ ! (ProcTable.ps(@pool_master_pid).cmdline =~ /resque-pool-master\[resque-cluster\]:\smanaging\s\[/))
59
+ @pool_master_pid = nil
60
+ end
42
61
  end
43
62
  @pid = nil
44
- sleep(5)
63
+ sleep(3)
45
64
  end
46
65
 
47
66
  def counts
48
67
  return {} unless @pool_master_pid
49
- local_workers = `ps --ppid #{@pool_master_pid} -fh | awk '{print $8}'`.split
68
+ local_workers = ProcTable.ps.select{|p| p.ppid == @pool_master_pid}.map(&:cmdline)
50
69
  TestMemberManager.worker_counts(local_workers)
51
70
  end
52
71
 
53
72
  def self.counts
54
- all_workers = `ps -ef | grep "resque-" | grep "Waiting for" | grep -v ps| awk '{print $11}'`.split
73
+ pool_pids = resque_cluster_members.map(&:pid)
74
+ all_workers = ProcTable.ps.select{|p| pool_pids.include? p.ppid}.map(&:cmdline)
55
75
  worker_counts(all_workers)
56
76
  end
57
77
 
58
78
  def self.worker_counts(worker_array)
59
79
  final_counts = Hash.new(0)
60
80
 
61
- worker_array.each do |worker|
81
+ worker_array.each do |worker_cmdline|
82
+ worker = parse_worker_name(worker_cmdline)
62
83
  final_counts[worker] += 1
63
84
  end
64
85
 
@@ -69,13 +90,23 @@ class TestMemberManager
69
90
  @hostname ||= "#{Socket.gethostname}-#{member_count+1}"
70
91
  end
71
92
 
93
+ def self.parse_worker_name(worker_cmdline)
94
+ worker_cmdline.gsub(/resque(\d|\.|-)*:\sWaiting\sfor\s/, '')
95
+ end
96
+
72
97
  def self.stop_all
73
- pools = `ps -ef | grep 'resque-pool-master\\[resque-cluster\\]: managing \\[' | awk '{print $2}'`.split
74
- `kill #{pools.join(' ')}` unless pools.empty?
98
+ TestMemberManager.resque_cluster_members.each do |member|
99
+ Process.kill(:TERM, member.pid)
100
+ end
75
101
  sleep(3)
76
102
  end
77
103
 
78
104
  def member_count
79
- `ps -ef | grep resque-pool-master | grep -v grep|wc -l`.strip.to_i
105
+ TestMemberManager.resque_cluster_members.count
106
+ end
107
+
108
+ def self.resque_cluster_members
109
+ ProcTable.ps.select{|p| p.cmdline =~ /resque-pool-master/}
80
110
  end
111
+
81
112
  end
@@ -0,0 +1,86 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ module Resque
4
+ RSpec.describe Cluster::Config::Verifier do
5
+ let(:valid_local_config) { Cluster::Config::File.new(support_dir + 'valid_local_config.yml') }
6
+ let(:valid_global_config) { Cluster::Config::File.new(support_dir + 'valid_global_config.yml') }
7
+ let(:broken_yaml_config) { Cluster::Config::File.new(support_dir + 'broken_yaml_config.yml') }
8
+ let(:non_hash_config) { Cluster::Config::File.new(support_dir + 'non_hash_config.yml') }
9
+ let(:empty_config) { Cluster::Config::File.new(support_dir + 'empty_config.yml') }
10
+ let(:missing_config) { Cluster::Config::File.new(support_dir + 'missing') }
11
+
12
+ subject { Cluster::Config::Verifier.new(configs) }
13
+
14
+ shared_examples_for 'valid config' do
15
+ it 'verifies' do
16
+ expect(subject).to be_verified
17
+ end
18
+ end
19
+
20
+ shared_examples_for 'invalid config' do
21
+ it "doesn't verify" do
22
+ expect(subject).not_to be_verified
23
+ end
24
+ end
25
+
26
+ context 'with valid configs' do
27
+ let(:configs) { [valid_local_config, valid_global_config] }
28
+
29
+ it_behaves_like 'valid config'
30
+ end
31
+
32
+ context 'with a single config file' do
33
+ let(:configs) { [valid_local_config] }
34
+
35
+ it_behaves_like 'valid config'
36
+ end
37
+
38
+ context 'with a missing config' do
39
+ let(:configs) { [missing_config] }
40
+
41
+ it_behaves_like 'invalid config'
42
+
43
+ it 'reports the error' do
44
+ subject.verified?
45
+
46
+ expect(subject.configs.flat_map { |config| config.errors.to_a }).to include("Configuration file doesn't exist")
47
+ end
48
+ end
49
+
50
+ context 'with a non-hash config' do
51
+ let(:configs) { [non_hash_config] }
52
+
53
+ it_behaves_like 'invalid config'
54
+
55
+ it 'reports the error' do
56
+ subject.verified?
57
+
58
+ expect(subject.configs.flat_map { |config| config.errors.to_a }).to include("Parsed config as invalid type: expected Hash, got FalseClass")
59
+ end
60
+ end
61
+
62
+ context 'with an empty config' do
63
+ let(:configs) { [empty_config] }
64
+
65
+ it_behaves_like 'invalid config'
66
+
67
+ it 'reports the error' do
68
+ subject.verified?
69
+
70
+ expect(subject.configs.flat_map { |config| config.errors.to_a }).to include("Config file is empty")
71
+ end
72
+ end
73
+
74
+ context 'with broken YAML' do
75
+ let(:configs) { [broken_yaml_config] }
76
+
77
+ it_behaves_like 'invalid config'
78
+
79
+ it 'reports the error' do
80
+ subject.verified?
81
+
82
+ expect(subject.configs.flat_map { |config| config.errors.to_a }).to include('(<unknown>): did not find expected comment or line break while scanning a block scalar at line 1 column 1')
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,194 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ RSpec.describe Resque::Cluster::Config do
4
+ let(:redis) { Resque.redis }
5
+
6
+ before do
7
+ config.verified?
8
+
9
+ Resque::Cluster.config = {
10
+ cluster_name: 'unit-test-cluster',
11
+ environment: 'unit-test'
12
+ }
13
+ end
14
+
15
+ shared_examples_for 'a valid config' do
16
+ it 'is verified' do
17
+ expect(config).to be_verified
18
+ end
19
+
20
+ it "gru_format should return a correct hash" do
21
+ expect(config.gru_format).to eql(correct_hash)
22
+ end
23
+ end
24
+
25
+ describe 'with a single config file' do
26
+ let(:config) { Resque::Cluster::Config.new(config_path) }
27
+ let(:correct_hash) do
28
+ {
29
+ cluster_maximums: { 'foo' => 2, 'bar' => 50, "foo,bar,baz" => 1 },
30
+ host_maximums: { 'foo' => 1, 'bar' => 9, "foo,bar,baz" => 1 },
31
+ client_settings: redis.client.options,
32
+ rebalance_flag: true,
33
+ presume_host_dead_after: 60,
34
+ max_workers_per_host: 10,
35
+ cluster_name: "unit-test-cluster",
36
+ environment_name: "unit-test",
37
+ manage_worker_heartbeats: true,
38
+ version_hash: `git rev-parse --verify HEAD`.strip
39
+ }
40
+ end
41
+
42
+ context 'old style' do
43
+ let(:config_path) { support_dir + 'valid_single_old_config.yml' }
44
+
45
+ it_behaves_like 'a valid config'
46
+
47
+ context 'with a missing local maximum' do
48
+ let(:config) { Resque::Cluster::Config.new(config_path) }
49
+ let(:config_path) { support_dir + 'single_old_missing_local.yml' }
50
+
51
+ it 'should not be verified' do
52
+ expect(config).not_to be_verified
53
+ end
54
+
55
+ it 'should not be verified' do
56
+ expect(config.errors).to contain_exactly("Every worker configuration must contain a local and a global maximum.")
57
+ end
58
+ end
59
+
60
+ context 'with a missing global maximum' do
61
+ let(:config) { Resque::Cluster::Config.new(config_path) }
62
+ let(:config_path) { support_dir + 'single_old_missing_global.yml' }
63
+
64
+ it 'should not be verified' do
65
+ expect(config).not_to be_verified
66
+ end
67
+
68
+ it 'should not be verified' do
69
+ expect(config.errors).to contain_exactly("Every worker configuration must contain a local and a global maximum.")
70
+ end
71
+ end
72
+ end
73
+
74
+ context 'new style' do
75
+ let(:config_path) { support_dir + 'valid_single_new_config.yml' }
76
+
77
+ it_behaves_like 'a valid config'
78
+
79
+ context 'with a missing local maximum' do
80
+ let(:config) { Resque::Cluster::Config.new(config_path) }
81
+ let(:config_path) { support_dir + 'missing_local_maximum.yml' }
82
+
83
+ it 'should not be verified' do
84
+ expect(config).not_to be_verified
85
+ end
86
+
87
+ it 'should not be verified' do
88
+ expect(config.errors).to contain_exactly("Every worker configuration must contain a local and a global maximum.")
89
+ end
90
+ end
91
+
92
+ context 'with a missing global maximum' do
93
+ let(:config) { Resque::Cluster::Config.new(config_path) }
94
+ let(:config_path) { support_dir + 'missing_global_maximum.yml' }
95
+
96
+ it 'should not be verified' do
97
+ expect(config).not_to be_verified
98
+ end
99
+
100
+ it 'should not be verified' do
101
+ expect(config.errors).to contain_exactly("Every worker configuration must contain a local and a global maximum.")
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ describe 'separate config files' do
108
+ context "with valid config files" do
109
+ let(:config) { Resque::Cluster::Config.new(local_config_path, global_config_path) }
110
+ let(:local_config_path) { File.expand_path(File.dirname(__FILE__) + '/../local_config.yml') }
111
+ let(:global_config_path) { File.expand_path(File.dirname(__FILE__) + '/../global_config.yml') }
112
+
113
+ let(:correct_hash) do
114
+ {
115
+ cluster_maximums: { 'foo' => 2, 'bar' => 50, "foo,bar,baz" => 1 },
116
+ host_maximums: { 'foo' => 1, 'bar' => 9, "foo,bar,baz" => 1 },
117
+ client_settings: redis.client.options,
118
+ rebalance_flag: false,
119
+ presume_host_dead_after: 120,
120
+ max_workers_per_host: nil,
121
+ cluster_name: "unit-test-cluster",
122
+ environment_name: "unit-test",
123
+ manage_worker_heartbeats: true,
124
+ version_hash: `git rev-parse --verify HEAD`.strip
125
+ }
126
+ end
127
+
128
+ it_behaves_like 'a valid config'
129
+
130
+ it "config should have no warnings or errors" do
131
+ expect(config.errors.count).to eql(0)
132
+ expect(config.warnings.count).to eql(0)
133
+ end
134
+
135
+ it "git_version_hash should be set" do
136
+ expect(config.version_git_hash).to eql(`git rev-parse --verify HEAD`.strip)
137
+ end
138
+ end
139
+
140
+ context 'with a missing local maximum' do
141
+ let(:config) { Resque::Cluster::Config.new(local_config_path, global_config_path) }
142
+ let(:local_config_path) { support_dir + 'separate_missing_local.yml' }
143
+ let(:global_config_path) { File.expand_path(File.dirname(__FILE__) + '/../global_config.yml') }
144
+
145
+ it 'should not be verified' do
146
+ expect(config).not_to be_verified
147
+ end
148
+
149
+ it 'should not be verified' do
150
+ expect(config.errors).to contain_exactly("Every worker configuration must contain a local and a global maximum.")
151
+ end
152
+ end
153
+
154
+ context 'with a missing global maximum' do
155
+ let(:config) { Resque::Cluster::Config.new(local_config_path, global_config_path) }
156
+ let(:local_config_path) { File.expand_path(File.dirname(__FILE__) + '/../local_config.yml') }
157
+ let(:global_config_path) { support_dir + 'separate_missing_global.yml' }
158
+
159
+ it 'should not be verified' do
160
+ expect(config).not_to be_verified
161
+ end
162
+
163
+ it 'should not be verified' do
164
+ expect(config.errors).to contain_exactly("Every worker configuration must contain a local and a global maximum.")
165
+ end
166
+ end
167
+
168
+ context "with invalid config file" do
169
+ let(:local_config_path) { File.expand_path(File.dirname(__FILE__) + '/../local_configuration.yml') }
170
+ let(:global_config_path) { File.expand_path(File.dirname(__FILE__) + '/../../README.rdoc') }
171
+ let(:config) { Resque::Cluster::Config.new(local_config_path, global_config_path) }
172
+ let(:correct_hash) { {} }
173
+
174
+ it "should not be verified" do
175
+ expect(config.verified?).to eql(false)
176
+ end
177
+
178
+ it "config should have no warnings but 2 errors" do
179
+ expect(config.errors.count).to eql(1)
180
+ expect(config.warnings.count).to eql(0)
181
+ expect(config.errors).to contain_exactly(
182
+ "#{local_config_path}: Configuration file doesn't exist")
183
+ end
184
+
185
+ it "gru_format should return a an empty hash" do
186
+ expect(config.gru_format).to eql(correct_hash)
187
+ end
188
+
189
+ it "git_version_hash should not be set" do
190
+ expect(config.version_git_hash).to be_nil
191
+ end
192
+ end
193
+ end
194
+ end