qtrix 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+ require 'stringio'
3
+ require 'qtrix/cli'
4
+
5
+ shared_context "cli commands" do
6
+ let(:stdout_stream) {StringIO.new}
7
+ let(:stderr_stream) {StringIO.new}
8
+
9
+ def stdout
10
+ stdout_stream.rewind
11
+ stdout_stream.read
12
+ end
13
+
14
+ def stderr
15
+ stderr_stream.rewind
16
+ stderr_stream.read
17
+ end
18
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe Qtrix::Matrix::Analyzer do
4
+ before(:each) do
5
+ Qtrix.map_queue_weights A: 40, B: 30, C: 20, D: 10
6
+ end
7
+ let(:matrix) {Qtrix::Matrix.queues_for!("host1", 4)}
8
+
9
+ describe "#breakdown" do
10
+ it "results in hash of queue names to arrays of counts in each column in the matrix" do
11
+ result = Qtrix::Matrix::Analyzer.breakdown(matrix)
12
+ result.should == {
13
+ A: [1,3,0,0],
14
+ B: [1,1,2,0],
15
+ C: [1,0,2,1],
16
+ D: [1,0,0,3]
17
+ }
18
+ result.dump
19
+ end
20
+ end
21
+
22
+ describe "#analyze!" do
23
+ it "should map queue weights, populate matrix then break it down." do
24
+ expected = {
25
+ A: [1,3,0,0],
26
+ B: [1,1,2,0],
27
+ C: [1,0,2,1],
28
+ D: [1,0,0,3]
29
+ }
30
+ result = Qtrix::Matrix::Analyzer.analyze! 4, \
31
+ A: 40,
32
+ B: 30,
33
+ C: 20,
34
+ D: 10
35
+ result.should == expected
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ describe Qtrix::Matrix do
4
+ let(:matrix) {Qtrix::Matrix}
5
+ before(:each) do
6
+ Qtrix::Matrix.clear!
7
+ Qtrix.map_queue_weights \
8
+ A: 40,
9
+ B: 30,
10
+ C: 20,
11
+ D: 10
12
+ end
13
+
14
+ describe "#queues_for!" do
15
+ context "with no rows" do
16
+ it "should generate new rows" do
17
+ result = matrix.queues_for!('host1', 1)
18
+ result.should == [[:A, :B, :C, :D]]
19
+ end
20
+
21
+ it "should populate the persistant model" do
22
+ result = matrix.queues_for!('host1', 1)
23
+ result.should == matrix.to_table
24
+ end
25
+ end
26
+
27
+ context "with existing rows" do
28
+ it "should maintain existing rows if no more needed" do
29
+ matrix.queues_for!('host1', 1)
30
+ matrix.queues_for!('host1', 1)
31
+ matrix.fetch.size.should == 1
32
+ end
33
+
34
+ it "should add rows if more needed" do
35
+ matrix.queues_for!('host1', 1)
36
+ matrix.queues_for!('host1', 2)
37
+ matrix.fetch.size.should == 2
38
+ end
39
+
40
+ it "should prune rows if less are needed" do
41
+ matrix.queues_for!('host1', 2)
42
+ matrix.queues_for!('host1', 1)
43
+ matrix.fetch.size.should == 1
44
+ end
45
+ end
46
+
47
+ context "with multiple hosts" do
48
+ before do
49
+ matrix.queues_for!('host1', 2)
50
+ matrix.queues_for!('host2', 2)
51
+ end
52
+
53
+ let(:host1_rows) {matrix.fetch.select{|row| row.hostname == 'host1'}}
54
+ let(:host2_rows) {matrix.fetch.select{|row| row.hostname == 'host2'}}
55
+
56
+ context "when rows are added" do
57
+ it "should associate them with the specific host" do
58
+ matrix.queues_for!('host1', 3)
59
+ host1_rows.size.should == 3
60
+ host2_rows.size.should == 2
61
+ end
62
+ end
63
+
64
+ context "when rows are pruned" do
65
+ it "should prune them from the specific host" do
66
+ matrix.queues_for!('host2', 1)
67
+ host1_rows.size.should == 2
68
+ host2_rows.size.should == 1
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+ require 'rspec-prof'
3
+
4
+ describe Qtrix::Matrix do
5
+ # For this test's purpose, a queues receives a score each time it appears
6
+ # in a row within the matrix -- 4 for being at the head of a row, 3 for
7
+ # being the next queue in the row, 2 for being the 3rd queue in the row
8
+ # and 1 for being the last queue in the row.
9
+ def cumulative_score_of(queue)
10
+ matrix = Qtrix::Matrix.to_table
11
+ scores = matrix.map{|row| 4 - row.index(queue)}
12
+ scores.inject(0) {|m, s| m += s}
13
+ end
14
+
15
+ def head_count_of(target_queue)
16
+ matrix = Qtrix::Matrix.to_table
17
+ heads = matrix.map{|row| row[0]}
18
+ heads.select{|queue| queue == target_queue}.size
19
+ end
20
+
21
+ let(:a_score) {cumulative_score_of(:A)}
22
+ let(:b_score) {cumulative_score_of(:B)}
23
+ let(:c_score) {cumulative_score_of(:C)}
24
+ let(:d_score) {cumulative_score_of(:D)}
25
+ let(:a_heads) {head_count_of(:A)}
26
+ let(:b_heads) {head_count_of(:B)}
27
+ let(:c_heads) {head_count_of(:C)}
28
+ let(:d_heads) {head_count_of(:D)}
29
+
30
+ # Check to make sure that the following holds true for 4, 10 and 100
31
+ # worker setups:
32
+ #
33
+ # 1. every queue is at the head of a worker list at least once.
34
+ # 2. queues with a higher weight occur more frequently to the left of queues
35
+ # with a lower weight in the list of queues processed by all workers.
36
+ [2,5,50].each do |worker_count|
37
+ context "managing #{worker_count*2} workers across 2 hosts" do
38
+ # The following will generate profiling reports in the profiles dir.
39
+ profile(:all) do
40
+ before (:each) do
41
+ Qtrix::Matrix.clear!
42
+ Qtrix.map_queue_weights \
43
+ A: 40,
44
+ B: 30,
45
+ C: 20,
46
+ D: 10
47
+ Qtrix::Matrix.queues_for!('host1', worker_count)
48
+ Qtrix::Matrix.queues_for!('host2', worker_count)
49
+ end
50
+
51
+ it "should maintain the desired distribution of queues" do
52
+ a_score.should be >= b_score
53
+ b_score.should be >= c_score
54
+ c_score.should be >= d_score
55
+ end
56
+
57
+ it "should have every queue at the head of at least one worker's queue list" do
58
+ a_heads.should_not == 0
59
+ b_heads.should_not == 0
60
+ c_heads.should_not == 0
61
+ d_heads.should_not == 0
62
+ end
63
+
64
+ it "should maintain desired distribution of queues at the heead of queue lists" do
65
+ a_heads.should be >= b_heads
66
+ b_heads.should be >= c_heads
67
+ c_heads.should be >= d_heads
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+ require 'set'
3
+
4
+ describe Qtrix::Matrix do
5
+ describe "namespaced operations" do
6
+ include_context "established default and night namespaces"
7
+ let(:matrix) {Qtrix::Matrix}
8
+
9
+ describe "#fetch" do
10
+ it "should default to the current namespace" do
11
+ result = matrix.fetch.map{|row| row.entries.map(&:queue)}.sort
12
+ result.should == [[:A, :B, :C]]
13
+ end
14
+
15
+ it "should allow fetching from a different namespace" do
16
+ result = matrix.fetch(:night).map{|row| row.entries.map(&:queue)}.sort
17
+ result.should == [[:X, :Y, :Z]]
18
+ end
19
+ end
20
+
21
+ describe "#to_table" do
22
+ it "should default to the current namespace" do
23
+ matrix.to_table.should == [[:A, :B, :C]]
24
+ end
25
+
26
+ it "should allow fetching from a different namespace" do
27
+ matrix.to_table(:night).should == [[:X, :Y, :Z]]
28
+ end
29
+ end
30
+
31
+ describe "#clear!" do
32
+ it "should default to the current namespace" do
33
+ matrix.clear!
34
+ raw_redis.keys("qtrix:default:matrix*").should == []
35
+ end
36
+
37
+ it "should allow clearing of a different namespace" do
38
+ matrix.clear! :night
39
+ raw_redis.keys("qtrix:night:matrix*").should == []
40
+ end
41
+ end
42
+
43
+ describe "#queues_for!" do
44
+ context "with no namespace specified" do
45
+ it "should return queues from current namespace" do
46
+ matrix.queues_for!("host1", 1).should == [[:A, :B, :C]]
47
+ end
48
+
49
+ it "should auto-shuffle distribution of queues if they all have the same weight" do
50
+ Qtrix.map_queue_weights A: 1, B: 1, C: 1, D: 1, E: 1
51
+ matrix.queues_for!("host1", 100)
52
+ rows = matrix.to_table[5..-1]
53
+ dupes = Set.new
54
+ while(current_row = rows.shift) do
55
+ next if dupes.include? current_row
56
+ if rows.detect{|another_row| another_row == current_row}
57
+ dupes.add(current_row)
58
+ end
59
+ end
60
+ dupes.size.should be < 10
61
+ end
62
+ end
63
+
64
+ context "with namespace specified" do
65
+ it "should return queues from the specified namespace" do
66
+ matrix.queues_for!(:night, "host1", 1).should == [[:X, :Y, :Z]]
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,207 @@
1
+ require 'spec_helper'
2
+
3
+ describe Qtrix::Namespacing do
4
+ include Qtrix::Namespacing
5
+ let(:test_class) {
6
+ class Foo < Object
7
+ include Qtrix::Namespacing
8
+ end
9
+ }
10
+ let(:test_instance) {test_class.new}
11
+
12
+ shared_examples_for "@redis_namespace definers #redis" do
13
+ it "should return redis connection namespaced to :a" do
14
+ target.redis.namespace.should == :a
15
+ end
16
+
17
+ it "should allow caller to specify namespacing" do
18
+ target.redis(:b, :c).set("d", 1)
19
+ result = raw_redis.keys.reject{|key| key[/namespace/]}
20
+ result.should == ["qtrix:default:a:b:c:d"]
21
+ end
22
+ end
23
+
24
+ [:test_class, :test_instance].each do |target_name|
25
+ context "for #{target_name}" do
26
+ let(:target) {send(target_name)}
27
+
28
+ describe "#redis" do
29
+ context "without @redis_namespace defined in self or self.class" do
30
+ it "should return redis connection namespaced to :default" do
31
+ target.redis.namespace.should == :default
32
+ end
33
+
34
+ it "should allow caller to specify namespacing" do
35
+ target.redis(:a, :b).set("c", 1)
36
+ raw_redis.keys.include?("qtrix:a:b:c").should == true
37
+ end
38
+
39
+ it "should evaluate caller arg of :current to the current namespace" do
40
+ target.redis(:current).set("a", 1)
41
+ result = raw_redis.keys.select{|key| key[/default/]}
42
+ result.should == ["qtrix:default:a"]
43
+ end
44
+ end
45
+
46
+ context "with @redis_namespace defined in self" do
47
+ around(:each) do |example|
48
+ target.send(:instance_variable_set, :@redis_namespace, [:current, :a])
49
+ example.run
50
+ target.send(:instance_variable_set, :@redis_namespace, nil)
51
+ end
52
+
53
+ it_should_behave_like "@redis_namespace definers #redis"
54
+ end
55
+
56
+ context "with @redis_namespace defined in class" do
57
+ around(:each) do |example|
58
+ target.class.send(:instance_variable_set, :@redis_namespace, [:current, :a])
59
+ example.run
60
+ target.class.send(:instance_variable_set, :@redis_namespace, nil)
61
+ end
62
+
63
+ it_should_behave_like "@redis_namespace definers #redis"
64
+ end
65
+ end
66
+
67
+ describe "#redis_namespace" do
68
+ context "without @redis_namespace defined in self or self.class" do
69
+ it "should return nil" do
70
+ target.redis_namespace.should == []
71
+ end
72
+ end
73
+
74
+ context "with @redis_namespace defined" do
75
+ around(:each) do |example|
76
+ target.send(:instance_variable_set, :@redis_namespace, [:current, :foo])
77
+ example.run
78
+ target.send(:instance_variable_set, :@redis_namespace, nil)
79
+ end
80
+
81
+ it "should return self@redis_namespace" do
82
+ target.redis_namespace.should == [:current, :foo]
83
+ end
84
+ end
85
+
86
+ context "with @redis_namespace defined in superclass" do
87
+ around(:each) do |example|
88
+ target.class.send(:instance_variable_set, :@redis_namespace, [:current, :foo])
89
+ example.run
90
+ target.class.send(:instance_variable_set, :@redis_namespace, nil)
91
+ end
92
+
93
+ it "should return class@redis_namespace" do
94
+ target.redis_namespace.should == [:current, :foo]
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ describe Qtrix::Namespacing::Manager do
102
+ let(:manager) {Qtrix::Namespacing::Manager.instance}
103
+
104
+ describe "#redis" do
105
+ context "with no args passed" do
106
+ it "should return a redis connection with no args" do
107
+ manager.redis.keys.should_not be_nil
108
+ end
109
+
110
+ it "should return a redis connection namespaced to qtrix:default" do
111
+ manager.redis.namespace.should == :default
112
+ end
113
+ end
114
+
115
+ context "with args passed" do
116
+ it "each arg should be a namespace for keys defined in redis" do
117
+ manager.redis(:foo, :bar).set("a", 1)
118
+ raw_redis.keys.include?("qtrix:foo:bar:a").should == true
119
+ end
120
+
121
+ it "should prune out any duplicate namespaces" do
122
+ manager.redis(:a, :a, :a).set("b", 1)
123
+ result = raw_redis.keys.grep(/qtrix:a/)
124
+ result.should == ["qtrix:a:b"]
125
+ end
126
+ end
127
+ end
128
+
129
+ describe "#add_namespace" do
130
+ it "should add a namespace to the system" do
131
+ manager.add_namespace(:night_distribution)
132
+ manager.namespaces.sort.should == [:default, :night_distribution]
133
+ end
134
+
135
+ it "should error when nil passed" do
136
+ expect{manager.add_namespace(nil)}.to raise_error
137
+ end
138
+
139
+ it "should error when valid pattern is not matched" do
140
+ expect{manager.add_namespace('#$*#($')}.to raise_error
141
+ end
142
+
143
+ it "should error when attempting to add an existing namespace" do
144
+ manager.namespaces.should_not be_empty
145
+ expect{manager.add_namespace(:default)}.to raise_error
146
+ end
147
+ end
148
+
149
+ describe "#remove_namesapce" do
150
+ include_context "an established matrix"
151
+ before {manager.add_namespace(:transition_flood)}
152
+
153
+ it "should remove a namespace from the system" do
154
+ manager.remove_namespace!(:transition_flood)
155
+ manager.namespaces.should == [:default]
156
+ end
157
+
158
+ it "should not allow you to delete the default namespace" do
159
+ expect{manager.remove_namespace!(:default)}.to raise_error
160
+ end
161
+
162
+ it "should not allow you to remove the current namespace" do
163
+ Qtrix.map_queue_weights(:transition_flood, A: 1)
164
+ manager.change_current_namespace(:transition_flood)
165
+ expect{manager.remove_namespace!(:transition_flood)}.to raise_error
166
+ end
167
+
168
+ describe "cascading removal" do
169
+ before do
170
+ Qtrix.add_override(:transition_flood, ["A"], 1)
171
+ Qtrix.map_queue_weights :transition_flood, B: 10
172
+ Qtrix::Matrix.queues_for!(:transition_flood, "host1", 2)
173
+ end
174
+
175
+ it "should cascade deletes to data in the namespace" do
176
+ manager.remove_namespace!(:transition_flood)
177
+ raw_redis.keys("qtrix:transition_flood*").should == []
178
+ end
179
+ end
180
+ end
181
+
182
+ describe "#current_namespace" do
183
+ it "should default to :default" do
184
+ manager.current_namespace.should == :default
185
+ end
186
+ end
187
+
188
+ describe "#change_current_namespace" do
189
+ include_context "an established matrix"
190
+ before(:each) {manager.add_namespace(:night_distribution)}
191
+
192
+ it "should set the current namespace" do
193
+ Qtrix.map_queue_weights(:night_distribution, A: 1)
194
+ manager.change_current_namespace(:night_distribution)
195
+ manager.current_namespace.should == :night_distribution
196
+ end
197
+
198
+ it "should error if trying to set the namespace to something unknown" do
199
+ expect{manager.change_current_namespace :foo}.to raise_error
200
+ end
201
+
202
+ it "should error if trying to change into an empty namespace" do
203
+ expect{manager.change_current_namespace(:night_distribution)}.to raise_error
204
+ end
205
+ end
206
+ end
207
+ end