sp-squealer 1.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.
@@ -0,0 +1,254 @@
1
+ require 'spec_helper'
2
+
3
+ describe Squealer::ProgressBar do
4
+ let(:clock) { Time.new }
5
+ let(:total) { 200 }
6
+ let(:progress_bar) do
7
+ testable_progress_bar = Class.new(Squealer::ProgressBar) do
8
+ attr_reader :emitter
9
+ attr_accessor :clock
10
+ public :total, :ticks, :percentage, :progress_markers, :emit,
11
+ :duration, :start_time, :end_time, :progress_bar_width
12
+
13
+ def console
14
+ @console ||= StringIO.new
15
+ end
16
+
17
+ alias real_start_emitter start_emitter
18
+ def start_emitter; end
19
+ public :real_start_emitter
20
+
21
+ end
22
+ bar = testable_progress_bar.new(total)
23
+ bar.clock = clock
24
+ bar.start
25
+ end
26
+ let(:console) { progress_bar.console }
27
+ let(:progress_bar_width) { progress_bar.progress_bar_width }
28
+
29
+ before { progress_bar.start }
30
+ after { progress_bar.finish }
31
+
32
+ it "allows only one progress bar at a time" do
33
+ Squealer::ProgressBar.new(0).should be_nil
34
+ end
35
+
36
+ it "records the starting time" do
37
+ progress_bar.start_time.should be_an_instance_of(Time)
38
+ end
39
+
40
+ it "records the starting time" do
41
+ progress_bar.start_time.should be_an_instance_of(Time)
42
+ end
43
+
44
+ context "threaded" do
45
+ before { progress_bar.stub(:emitter).and_return(progress_bar.real_start_emitter) }
46
+ after { progress_bar.emitter.kill }
47
+
48
+ it "has an emitter" do
49
+ progress_bar.tick
50
+ progress_bar.emitter.should_not be_nil
51
+ end
52
+
53
+ it "emits at least once" do
54
+ progress_bar.tick
55
+ progress_bar.emitter.wakeup
56
+ sleep 0.1
57
+ console.string.split("\r").length.should > 0
58
+ end
59
+ end
60
+
61
+ context "no items completed" do
62
+ it "emits the total number provided" do
63
+ progress_bar.total.should == total
64
+ end
65
+
66
+ it "emits the number of ticks" do
67
+ progress_bar.ticks.should == 0
68
+ end
69
+
70
+ it "displays an empty bar" do
71
+ progress_bar.progress_markers.size.should == 0
72
+ end
73
+
74
+ it "prints a progress bar to the console" do
75
+ progress_bar.emit
76
+ console.string.should == "\r[#{' ' * progress_bar_width}] #{0}/#{total} (0%) [0/s]"
77
+ end
78
+
79
+ context "0 ticks per second" do
80
+ it "shows 0 documents per second" do
81
+ progress_bar.clock = progress_bar.clock + 60
82
+ progress_bar.emit
83
+ console.string.should =~ %r{ \[0/s\]$}
84
+
85
+ progress_bar.clock = progress_bar.clock + 60
86
+ progress_bar.emit
87
+ console.string.should =~ %r{ \[0/s\]$}
88
+ end
89
+ end
90
+
91
+ context "1 ticks per second" do
92
+ it "shows 1 document per second" do
93
+ progress_bar.clock = progress_bar.clock + 60
94
+ progress_bar.tick
95
+ progress_bar.emit
96
+ console.string.should =~ %r{ \[1/s\]$}
97
+
98
+ progress_bar.clock = progress_bar.clock + 60
99
+ progress_bar.tick
100
+ progress_bar.emit
101
+ console.string.should =~ %r{ \[1/s\]$}
102
+ end
103
+ end
104
+
105
+ context "2 ticks per second" do
106
+ it "shows 2 documents per second" do
107
+ progress_bar.clock = progress_bar.clock + 60
108
+ progress_bar.tick
109
+ progress_bar.tick
110
+ progress_bar.emit
111
+ console.string.should =~ %r{ \[2/s\]$}
112
+
113
+ progress_bar.clock = progress_bar.clock + 60
114
+ progress_bar.tick
115
+ progress_bar.tick
116
+ progress_bar.emit
117
+ console.string.should =~ %r{ \[2/s\]$}
118
+ end
119
+ end
120
+
121
+ context "averaging 2 ticks per second" do
122
+ it "shows 2 documents per second" do
123
+ progress_bar.clock = progress_bar.clock + 60
124
+ progress_bar.tick
125
+ progress_bar.emit
126
+
127
+ progress_bar.clock = progress_bar.clock + 60
128
+ progress_bar.tick
129
+ progress_bar.emit
130
+
131
+ progress_bar.clock = progress_bar.clock + 60
132
+ 4.times { progress_bar.tick }
133
+ progress_bar.emit
134
+ console.string.should =~ %r{ \[2/s\]$}
135
+ end
136
+ end
137
+ end
138
+
139
+ context "one item completed" do
140
+ before { progress_bar.tick }
141
+ it "emits the number of ticks" do
142
+ progress_bar.ticks.should == 1
143
+ end
144
+
145
+ it "emits the number of ticks as a percentage of the total number (rounded down)" do
146
+ progress_bar.percentage.should == 0
147
+ end
148
+ end
149
+
150
+ context "1/nth complete (where n is the width of the progress bar)" do
151
+ let(:ticks) { (total / progress_bar_width) }
152
+ before { ticks.times { progress_bar.tick } }
153
+ it "emits the number of ticks" do
154
+ progress_bar.ticks.should == ticks
155
+ end
156
+
157
+ it "emits the number of ticks as a percentage of the total number (rounded down)" do
158
+ progress_bar.percentage.should == (ticks.to_f * 100 / total).floor
159
+ end
160
+
161
+ it "displays the first progress marker" do
162
+ progress_bar.progress_markers.size.should == 1
163
+ end
164
+
165
+ it "prints a progress bar to the console" do
166
+ progress_bar.emit
167
+ percentage = (ticks.to_f * 100 / total).floor
168
+ console.string.should == "\r[=#{' ' * (progress_bar_width - 1)}] #{ticks}/#{total} (#{percentage}%) [0/s]"
169
+ end
170
+ end
171
+
172
+ context "all but one item completed" do
173
+ let(:ticks) { total - 1 }
174
+ before { ticks.times { progress_bar.tick } }
175
+
176
+ it "emits the number of ticks" do
177
+ progress_bar.ticks.should == ticks
178
+ end
179
+
180
+ it "emits the number of ticks as a percentage of the total number (rounded down)" do
181
+ progress_bar.percentage.should == 99
182
+ end
183
+
184
+ it "has not yet displayed the final progress marker" do
185
+ progress_bar.progress_markers.size.should == (progress_bar_width - 1)
186
+ end
187
+ end
188
+
189
+ context "all items completed" do
190
+ let(:ticks) { total }
191
+ before { ticks.times { progress_bar.tick } }
192
+
193
+ it "emits the number of ticks" do
194
+ progress_bar.ticks.should == ticks
195
+ end
196
+
197
+ it "emits 100%" do
198
+ progress_bar.percentage.should == 100
199
+ end
200
+
201
+ it "fills the progress bar with progress markers" do
202
+ progress_bar.progress_markers.size.should == progress_bar_width
203
+ end
204
+
205
+ it "records the ending time when finished" do
206
+ progress_bar.finish
207
+ progress_bar.end_time.should be_an_instance_of(Time)
208
+ end
209
+
210
+ it "prints a progress bar to the console" do
211
+ progress_bar.finish
212
+ progress_bar.emit
213
+ console.string.split("\n").first.should == "\r[#{'=' * progress_bar_width}] #{ticks}/#{total} (100%) [0/s]"
214
+ end
215
+ end
216
+
217
+ context "multiple emits" do
218
+ let(:ticks) { total }
219
+ subject { console.string }
220
+ before do
221
+ progress_bar.emit
222
+ end
223
+
224
+ context "not done" do
225
+ it "emitted two lines with no final newline" do
226
+ progress_bar.emit
227
+
228
+ subject.split("\r").size.should == 3
229
+ subject[-1, 1].should_not == "\n"
230
+ end
231
+ end
232
+
233
+ context "done" do
234
+ it "emitted two lines with a final newline" do
235
+ ticks.times { progress_bar.tick }
236
+ progress_bar.finish
237
+ progress_bar.emit
238
+
239
+ subject.split("\r").size.should == 3
240
+ subject[-1, 1].should == "\n"
241
+ end
242
+
243
+ it "emitted final timings" do
244
+ ticks.times { progress_bar.tick }
245
+ progress_bar.finish
246
+ progress_bar.emit
247
+
248
+ subject.should include("Start: #{progress_bar.start_time}\n")
249
+ subject.should include("End: #{progress_bar.end_time}\n")
250
+ subject.should include("Duration: #{progress_bar.duration}\n")
251
+ end
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,288 @@
1
+ require 'spec_helper'
2
+
3
+ describe Squealer::Target do
4
+ let(:table_name) { :test_table }
5
+ let(:test_table) { {'_id' => 0} }
6
+
7
+ let(:upsertable?) { Squealer::Database.instance.upsertable? }
8
+
9
+ after(:each) { Squealer::Target::Queue.instance.clear }
10
+
11
+ context "targeting" do
12
+ describe "initialize" do
13
+ let(:faqs) { [{'_id' => '123'}] }
14
+ let(:naqs) { [{:_id => 'abc'}] }
15
+
16
+ context "without a target row id" do
17
+ context "with the inferred variable in scope" do
18
+ it "infers the value from the _id field in the hashmap referenced by the variable" do
19
+ faqs.each do |faq|
20
+ Squealer::TestTarget.new(nil, :faq) do |target|
21
+ target.instance_variable_get('@row_id').should == faq['_id']
22
+ end
23
+ end
24
+ end
25
+ it "infers the value from the :_id field in the hashmap referenced by the variable" do
26
+ naqs.each do |naq|
27
+ Squealer::TestTarget.new(nil, :naq) do |target|
28
+ target.instance_variable_get('@row_id').should == naq[:_id]
29
+ end
30
+ end
31
+ end
32
+
33
+ context "but it doesn't have an _id key" do
34
+ it "throws an argument error" do
35
+ hash_with_no_id = {}
36
+ lambda do
37
+ Squealer::TestTarget.new(nil, :hash_with_no_id) {}
38
+ end.should raise_error(ArgumentError)
39
+ end
40
+ end
41
+
42
+ context "but it isn't a hashmap" do
43
+ it "throws an argument error" do
44
+ not_a_hash = nil
45
+ lambda do
46
+ Squealer::TestTarget.new(nil, :not_a_hash) {}
47
+ end.should raise_error(ArgumentError)
48
+ end
49
+ end
50
+ end
51
+
52
+ context "without the inferred variable in scope" do
53
+ it "throws a name error" do
54
+ lambda do
55
+ Squealer::TestTarget.new(nil, :missing_variable) {}
56
+ end.should raise_error(NameError)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+
64
+ context "nesting" do
65
+ it "pushes itself onto the targets stack when starting" do
66
+ @target1 = @target2 = nil
67
+
68
+ target1 = Squealer::TestTarget.new(nil, table_name) do
69
+ @target1 = Squealer::Target.current
70
+ test_table_2 = test_table
71
+ Squealer::TestTarget.new(nil, "#{table_name}_2") do
72
+ @target2 = Squealer::Target.current
73
+ @target2.should_not == @target1
74
+ end
75
+ end
76
+ target1.should === @target1
77
+ end
78
+
79
+ it "pops itself off the targets stack when finished" do
80
+ Squealer::TestTarget.new(nil, table_name) { nil }
81
+ Squealer::Target.current.should be_nil
82
+ end
83
+ end
84
+
85
+
86
+ context "yielding" do
87
+ it "yields" do
88
+ block_done = false
89
+ target = Squealer::TestTarget.new(nil, table_name) { block_done = true }
90
+ block_done.should be_true
91
+ end
92
+
93
+ it "yields inner blocks before executing its own SQL" do
94
+ blocks_done = []
95
+ Squealer::TestTarget.new(nil, table_name) do |target_1|
96
+ blocks_done << target_1
97
+ blocks_done.first.sql.should be_empty
98
+ Squealer::TestTarget.new(nil, table_name) do |target_2|
99
+ blocks_done << target_2
100
+ blocks_done.first.sql.should be_empty
101
+ blocks_done.last.sql.should be_empty
102
+ end
103
+ blocks_done.first.sql.should be_empty
104
+ blocks_done.last.sql.should_not be_empty
105
+ end
106
+ blocks_done.first.sql.should_not be_empty
107
+ blocks_done.last.sql.should_not be_empty
108
+ end
109
+ end
110
+
111
+
112
+ context "assigning" do
113
+ describe "#assign" do
114
+ let(:col1) { :meaning }
115
+ let(:value1) { 42 }
116
+ let(:faqs) { [{'_id' => nil, col1.to_s => value1}] }
117
+ let(:askers) { [{'_id' => 2001, 'name' => 'Zarathustra'}] }
118
+
119
+ context "with a block" do
120
+ it "uses the value from the block" do
121
+ faqs.each do |faq|
122
+ Squealer::TestTarget.new(nil, :faq) do
123
+ assign(col1) { value1 }
124
+ Squealer::Target.current.instance_variable_get('@column_names').should == [col1]
125
+ Squealer::Target.current.instance_variable_get('@column_values').should == [value1]
126
+ end
127
+ end
128
+ end
129
+ it "uses the value from the block even if it is nil" do
130
+ faqs.each do |faq|
131
+ Squealer::TestTarget.new(nil, :faq) do
132
+ assign(col1) { nil }
133
+ Squealer::Target.current.instance_variable_get('@column_names').should == [col1]
134
+ Squealer::Target.current.instance_variable_get('@column_values').should == [nil]
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ context "without a block" do
141
+ context "with the inferred variable in scope" do
142
+ it "infers source from target name" do
143
+ faqs.each do |faq|
144
+ Squealer::TestTarget.new(nil, :faq) do
145
+ assign(col1)
146
+ Squealer::Target.current.instance_variable_get('@column_names').should == [col1]
147
+ Squealer::Target.current.instance_variable_get('@column_values').should == [value1]
148
+ end
149
+ end
150
+ end
151
+ it "infers related source from target name" do
152
+ askers.each do |asker|
153
+ faqs.each do |faq|
154
+ Squealer::TestTarget.new(nil, :faq) do
155
+ assign(:asker_id)
156
+ Squealer::Target.current.instance_variable_get('@column_names').should == [:asker_id]
157
+ Squealer::Target.current.instance_variable_get('@column_values').should == [2001]
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ context "with an empty block" do
166
+ it "assumes nil" do
167
+ faqs.each do |faq|
168
+ Squealer::TestTarget.new(nil, :faq) do
169
+ assign(col1) {}
170
+ Squealer::Target.current.instance_variable_get('@column_names').should == [col1]
171
+ Squealer::Target.current.instance_variable_get('@column_values').should == [nil]
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+
180
+ context "exporting" do
181
+ it "sends the sql to the export database" do
182
+ Squealer::TestTarget.new(nil, table_name) { nil }
183
+ end
184
+
185
+ describe "#target" do
186
+ describe "#typecast_values" do
187
+ subject { target.send(:typecast_values) }
188
+ let(:target) { Squealer::TestTarget.new(nil, table_name) {} }
189
+
190
+ it "casts array to comma-separated string" do
191
+ target.assign(:colA) { ['1', '2'] }
192
+ subject.should == ['1,2']
193
+ end
194
+ end
195
+
196
+ context "generates SQL command strings" do
197
+ let(:target) { Squealer::TestTarget.new(nil, table_name) { nil } }
198
+
199
+ context "insert" do
200
+ it "targets the table" do
201
+ target.sql.should =~ /^INSERT INTO "#{table_name}" / if upsertable?
202
+ target.sql.should =~ /; INSERT INTO "#{table_name}" / unless upsertable?
203
+ end
204
+
205
+ it "includes the primary key name in the INSERT" do
206
+ target.sql.should =~ / \(id\) VALUES/
207
+ end
208
+
209
+ it "includes the primary key value in the INSERT" do
210
+ target.sql.should =~ / VALUES \('0'\)/
211
+ end
212
+ end
213
+
214
+ context "upsert" do
215
+ before { Squealer::Database.instance.should_receive(:upsertable?).at_least(:once).and_return(true) }
216
+ it "uses an INSERT ... ON DUPLICATE KEY UPDATE statement" do
217
+ target.sql.should =~ /^INSERT INTO .* ON DUPLICATE KEY UPDATE /
218
+ end
219
+ end
220
+ end
221
+
222
+ context "with 2 columns" do
223
+ let(:value_1) { 42 }
224
+ let(:target) do
225
+ Squealer::TestTarget.new(nil, table_name) { Squealer::Target.current.assign(:colA) { value_1 } }
226
+ end
227
+
228
+ context "insert" do
229
+ it "includes the column name in the INSERT" do
230
+ target.sql.should =~ /\(id,"colA"\) VALUES/
231
+ end
232
+
233
+ it "includes the column value in the INSERT" do
234
+ target.sql.should =~ /VALUES \('0',\?\)/
235
+ end
236
+ end
237
+
238
+ context "upsert" do
239
+ before { Squealer::Database.instance.should_receive(:upsertable?).at_least(:once).and_return(true) }
240
+ it "includes the column name and value in the UPDATE" do
241
+ target.sql.should =~ /UPDATE "colA"=\?/
242
+ end
243
+ end
244
+ end
245
+
246
+ context "with 3 columns" do
247
+ let(:value_1) { 42 }
248
+ let(:value_2) { 'foobar' }
249
+ let(:target) do
250
+ Squealer::TestTarget.new(nil, table_name) do |target|
251
+ Squealer::Target.current.assign(:colA) { value_1 }
252
+ Squealer::Target.current.assign(:colB) { value_2 }
253
+ end
254
+ end
255
+
256
+ context "insert" do
257
+ it "includes the column names in the INSERT" do
258
+ target.sql.should =~ /\(id,"colA","colB"\) VALUES/
259
+ end
260
+
261
+ it "includes the column values in the INSERT" do
262
+ target.sql.should =~ /VALUES \('0',\?,\?\)/
263
+ end
264
+ end
265
+
266
+ context "upsert" do
267
+ before { Squealer::Database.instance.should_receive(:upsertable?).at_least(:once).and_return(true) }
268
+ it "includes the column names and values in the UPDATE" do
269
+ target.sql.should =~ /UPDATE "colA"=\?,"colB"=\?/
270
+ end
271
+ end
272
+ end
273
+ end
274
+ end
275
+ end
276
+
277
+ # Yes, David Chelimsky, an any_instance stub would be handy right about now :-P
278
+ module Squealer
279
+ class TestTarget < Target
280
+ def execute_sql(sql, values)
281
+ end
282
+ end
283
+ end
284
+
285
+ module BSON
286
+ class ObjectID; end
287
+ end
288
+