spool_pool 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,113 @@
1
+ require 'yaml'
2
+ require 'spool_pool/file'
3
+
4
+ module SpoolPool
5
+ =begin rdoc
6
+ This class manages the data storage and retrieval within a specific spool.
7
+ =end
8
+ class Spool
9
+ attr_reader :pathname
10
+
11
+ =begin rdoc
12
+ Uses the directory given in +pathname+ as the directory for the subsequent
13
+ spooling operations.
14
+
15
+ Will perform a simple check on the directory and throw an exception if the
16
+ directory already exists but isn't read- and writeable by the effective
17
+ user id of the process.
18
+ =end
19
+ def initialize( pathname )
20
+ @pathname = pathname
21
+ validate_spool_directory
22
+ end
23
+
24
+ =begin rdoc
25
+ Serializes and stores the +data+.
26
+
27
+ Returns the path of the file storing the data.
28
+ =end
29
+ def put( data )
30
+ @pathname.mkpath unless @pathname.exist?
31
+ SpoolPool::File.write( @pathname, serialize( data ) )
32
+ end
33
+
34
+ =begin rdoc
35
+ Reads and returns the deserialized data of the oldest file in the spool,
36
+ yielding the deserialized data to an optionally passed block..
37
+
38
+ Deletes the file only if no exception was raised within the block.
39
+
40
+ Ordering is based on the filename (which in turn is based on the files
41
+ creation time), but the ordering is non-strict.
42
+
43
+ Data stored within the same second will be returned in a random order.
44
+ =end
45
+ def get(&block)
46
+ file = oldest_spooled_file
47
+ return nil unless file
48
+ data = if block_given?
49
+ SpoolPool::File.safe_read( file ) { |data| block.call( deserialize(data) ) }
50
+ else
51
+ SpoolPool::File.safe_read( file )
52
+ end
53
+ deserialize(data)
54
+ end
55
+
56
+ =begin rdoc
57
+ Retrieves and deserializes all the data in the spool, oldest data first.
58
+ Each piece of spooled data will be yielded to the supplied block.
59
+
60
+ Ordering is based on the files ctime, but the ordering is non-strict.
61
+ Data stored within the same second will be returned in a random order.
62
+ =end
63
+ def flush
64
+ loop do
65
+ data = get
66
+ break if data.nil?
67
+
68
+ yield data
69
+ end
70
+ end
71
+
72
+ =begin rdoc
73
+ Serializes the data so that it can be deserialized with the +deserialize+
74
+ method later on.
75
+ =end
76
+ def serialize( data )
77
+ self.class.serialize( data )
78
+ end
79
+
80
+ def self.serialize( data ) # :nodoc:
81
+ YAML.dump( data )
82
+ end
83
+
84
+ =begin rdoc
85
+ Deserializes the +data+ that has previously been serialized with +serialize+.
86
+ =end
87
+ def deserialize( data )
88
+ self.class.deserialize( data )
89
+ end
90
+
91
+ def self.deserialize( data ) # :nodoc:
92
+ YAML.load( data )
93
+ end
94
+
95
+ private
96
+ def oldest_spooled_file
97
+ @cached_entries_by_age ||= []
98
+ if @cached_entries_by_age.size == 0
99
+ @cached_entries_by_age = @pathname.children.select { |e| e.file? }.map{ |e| e.to_s }.sort
100
+ end
101
+ return nil if @cached_entries_by_age.size == 0
102
+ Pathname.new( @cached_entries_by_age.shift )
103
+ end
104
+
105
+ def validate_spool_directory
106
+ return unless @pathname.exist?
107
+
108
+ raise Errno::EACCES.new( "Spool directory '#{@pathname}' isn't writeable!" ) unless @pathname.writable?
109
+ raise Errno::EACCES.new( "Spool directory '#{@pathname}' isn't readable!" ) unless @pathname.readable?
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'benchmark'
4
+ require 'lib/spool_pool'
5
+
6
+ N = 10000
7
+
8
+ pool = SpoolPool::Pool.new 'test_spool'
9
+
10
+ Benchmark.bm( 7 ) do |bm|
11
+
12
+ bm.report( "Put:" ) { N.times { pool.put :my_spool, "foo" } }
13
+ bm.report( "Get:" ) { N.times { pool.get :my_spool } }
14
+ bm.report( "Mixed:" ) { N.times { pool.put :my_spool, "foo"; pool.get :my_spool } }
15
+
16
+ end
@@ -0,0 +1,10 @@
1
+ $: << File.expand_path( File.join( '..', 'lib' ), __FILE__ )
2
+ require 'spool_pool'
3
+
4
+ TEST_SPOOL_ROOT = File.expand_path( File.join( '..', 'test_spool' ) )
5
+
6
+ def with_fs_mode( pathname, mode )
7
+ pathname.chmod mode
8
+ yield
9
+ pathname.chmod 0755
10
+ end
@@ -0,0 +1,119 @@
1
+ require 'spec_helper'
2
+
3
+ describe SpoolPool::File do
4
+
5
+ before( :each ) do
6
+ @testspoolpath = File.join TEST_SPOOL_ROOT, "spool_files"
7
+ @basepath = Pathname.new( @testspoolpath )
8
+ @basepath.mkpath
9
+ @basepath.chmod 0755
10
+ @data = "my_data"
11
+ end
12
+
13
+ after( :each ) do
14
+ @basepath.rmtree
15
+ end
16
+
17
+ describe ".safe_read" do
18
+ before( :each ) do
19
+ @spoolfile = SpoolPool::File.write( @basepath, @data )
20
+ end
21
+
22
+ it "should return the files content data" do
23
+ SpoolPool::File.safe_read( @spoolfile ).should == @data
24
+ end
25
+
26
+ context "no block is passed" do
27
+ it "should not yield" do
28
+ SpoolPool::File.safe_read( @spoolfile )
29
+ end
30
+
31
+ it "should delete the file after reading the file" do
32
+ SpoolPool::File.safe_read( @spoolfile )
33
+ File.exist?( @spoolfile ).should be_false
34
+ end
35
+ end
36
+
37
+ context "a block is passed" do
38
+ it "should yield the read file data" do
39
+ SpoolPool::File.safe_read( @spoolfile ) do |read_data|
40
+ read_data.should == @data
41
+ end
42
+ end
43
+
44
+ context "no exception was raised in the block" do
45
+ before( :each ) do
46
+ @block = Proc.new {}
47
+ end
48
+
49
+ it "should delete the file after the block completes" do
50
+ SpoolPool::File.safe_read( @spoolfile, &@block )
51
+ File.exist?( @spoolfile ).should be_false
52
+ end
53
+ end
54
+
55
+ context "an exception was raised in the block" do
56
+ before( :each ) do
57
+ @block = Proc.new { raise RuntimeError }
58
+ end
59
+
60
+ it "should not delete the file after the block completes" do
61
+ lambda{ SpoolPool::File.safe_read( @spoolfile, &@block ) }
62
+ File.exist?( @spoolfile ).should be_true
63
+ end
64
+
65
+ it "should let the thrown exception bubble up further" do
66
+ lambda{ SpoolPool::File.safe_read( @spoolfile, &@block ) }.should raise_error( RuntimeError )
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ describe ".write" do
73
+ before( :each ) do
74
+ @failing_tempfile = SpoolPool::File.new( @basepath.to_s )
75
+ @failing_tempfile.stub!( :write ).and_raise( RuntimeError )
76
+ end
77
+
78
+ after( :each ) do
79
+ end
80
+
81
+ it "should create a filename in the given directory" do
82
+ SpoolPool::File.write( @basepath, @data ).should =~ /\A#{@basepath}/
83
+ end
84
+
85
+ it "should return the path for the file containing the spooled data" do
86
+ path = SpoolPool::File.write( @basepath, @data )
87
+ Pathname.new( path ).read.should == @data
88
+ end
89
+
90
+ it "should have a different filename for each file" do
91
+ spoolfile1 = SpoolPool::File.write( @basepath, @data )
92
+ spoolfile2 = SpoolPool::File.write( @basepath, @data )
93
+ spoolfile1.should_not == spoolfile2
94
+ end
95
+
96
+ it "should raise an exception if the file can't be created" do
97
+ with_fs_mode( @basepath, 0000 ) do
98
+ lambda { SpoolPool::File.write( @basepath, @data ) }.should raise_error
99
+ end
100
+ end
101
+
102
+ it "should raise an exception if the data can't be written" do
103
+ SpoolPool::File.stub!( :new ).and_return( @failing_tempfile )
104
+
105
+ lambda { SpoolPool::File.write( @basepath, @data ) }.should raise_error( RuntimeError )
106
+ end
107
+
108
+ context "on an aborted operation" do
109
+ before( :each ) do
110
+ SpoolPool::File.stub!( :new ).and_return( @failing_tempfile )
111
+ end
112
+
113
+ it "should delete any created file again" do
114
+ lambda { SpoolPool::File.write( @basepath, @data ) }.should raise_error( RuntimeError )
115
+ @basepath.children.should be_empty
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,492 @@
1
+ require 'spec_helper'
2
+
3
+ describe SpoolPool::Pool do
4
+ before( :each ) do
5
+ @root_pathname = Pathname.new( TEST_SPOOL_ROOT )
6
+ @spool_path = File.join( TEST_SPOOL_ROOT, "spooler" )
7
+ @spool_pathname = Pathname.new( @spool_path )
8
+ @spool_pathname.mkpath
9
+ @spool_pathname.chmod 0755
10
+
11
+ @instance = SpoolPool::Pool.new( @spool_path )
12
+ @spool = :my_spool
13
+ @data = 'some data'
14
+ end
15
+
16
+ after( :each ) do
17
+ @spool_pathname.chmod 0755 if @spool_pathname.exist?
18
+ @spool_pathname.rmtree if @spool_pathname.exist?
19
+ end
20
+
21
+ it "should have a spool_dir attribute" do
22
+ @instance.should respond_to( :spool_dir )
23
+ end
24
+
25
+ it "should store the spool_dir as a Pathname" do
26
+ @instance.spool_dir.should be_a( Pathname )
27
+ end
28
+
29
+ it "should have a spools attribute" do
30
+ @instance.should respond_to( :spools )
31
+ end
32
+
33
+ describe ".validate_pool_dir" do
34
+ context "if the pool directory does not exist, examine the parent dir" do
35
+ before( :each ) do
36
+ @spool_pathname.rmdir if @spool_pathname.exist?
37
+ end
38
+
39
+ after( :each ) do
40
+ @root_pathname.chmod 0755
41
+ end
42
+
43
+
44
+ context "it does not allow the pool directory to be created" do
45
+ before( :each ) do
46
+ @root_pathname.chmod 0555
47
+ end
48
+ it "should throw an exception" do
49
+ lambda { SpoolPool::Pool.validate_pool_dir( @spool_pathname.to_s ) }.should raise_error( Errno::EACCES )
50
+ end
51
+ end
52
+
53
+ context "if it is not executable" do
54
+ before( :each ) do
55
+ @root_pathname.chmod 0666
56
+ end
57
+
58
+ it "should throw an exception" do
59
+ lambda { SpoolPool::Pool.validate_pool_dir( @spool_pathname.to_s ) }.should raise_error
60
+ end
61
+ end
62
+
63
+ context "it allows the pool directory to be created" do
64
+ before( :each ) do
65
+ @root_pathname.chmod 0755
66
+ end
67
+
68
+ it "should not raise an exception" do
69
+ lambda { SpoolPool::Pool.validate_pool_dir( @spool_pathname.to_s ) }.should_not raise_error
70
+ end
71
+ end
72
+ end
73
+
74
+ context "if the pool directory exists" do
75
+ before( :each ) do
76
+ @spool_pathname.mkpath
77
+ @spool_pathname.chmod 0755
78
+ end
79
+
80
+ context "if it is not readable" do
81
+ before( :each ) do
82
+ @spool_pathname.chmod 0333
83
+ end
84
+
85
+ it "should throw an exception" do
86
+ lambda { SpoolPool::Pool.validate_pool_dir( @spool_pathname.to_s ) }.should raise_error
87
+ end
88
+ end
89
+
90
+ context "if it is not writeable" do
91
+ before( :each ) do
92
+ @spool_pathname.chmod 0555
93
+ end
94
+
95
+ it "should throw an exception" do
96
+ lambda { SpoolPool::Pool.validate_pool_dir( @spool_pathname.to_s ) }.should raise_error
97
+ end
98
+ end
99
+
100
+ context "if it is not executable" do
101
+ before( :each ) do
102
+ @spool_pathname.chmod 0666
103
+ end
104
+
105
+ it "should throw an exception" do
106
+ lambda { SpoolPool::Pool.validate_pool_dir( @spool_pathname.to_s ) }.should raise_error
107
+ end
108
+ end
109
+
110
+ context "if it is read- and writeable" do
111
+ context "if subdirectories exist" do
112
+ before( :each ) do
113
+ @spool_dir1 = @spool_pathname + "spool1"
114
+ @spool_dir1.mkpath
115
+ @spool_dir1.chmod 0755
116
+ @spool_dir2 = @spool_pathname + "spool2"
117
+ @spool_dir2.mkpath
118
+ @spool_dir2.chmod 0755
119
+ end
120
+
121
+ after( :each ) do
122
+ @spool_dir1.chmod 0755 if @spool_dir1.exist?
123
+ @spool_dir2.chmod 0755 if @spool_dir2.exist?
124
+ end
125
+
126
+ context "if any is not readable" do
127
+ before( :each ) do
128
+ @spool_dir1.chmod 0333
129
+ end
130
+
131
+ it "should throw an exception" do
132
+ lambda { SpoolPool::Pool.validate_pool_dir( @spool_pathname.to_s ) }.should raise_error
133
+ end
134
+ end
135
+
136
+ context "if any is not writeable" do
137
+ before( :each ) do
138
+ @spool_dir1.chmod 0555
139
+ end
140
+
141
+ it "should throw an exception" do
142
+ lambda { SpoolPool::Pool.validate_pool_dir( @spool_pathname.to_s ) }.should raise_error
143
+ end
144
+ end
145
+
146
+ context "if it is not executable" do
147
+ before( :each ) do
148
+ @spool_dir1.chmod 0666
149
+ end
150
+
151
+ it "should throw an exception" do
152
+ lambda { SpoolPool::Pool.validate_pool_dir( @spool_pathname.to_s ) }.should raise_error
153
+ end
154
+ end
155
+
156
+ context "if they are all read- and writeable" do
157
+ context "if there are files" do
158
+ before( :each ) do
159
+ @spool_file1 = @spool_dir1 + "file1"
160
+ @spool_file2 = @spool_dir1 + "file2"
161
+ FileUtils.touch @spool_file1.to_s
162
+ @spool_file1.chmod 0644
163
+ FileUtils.touch @spool_file2.to_s
164
+ @spool_file2.chmod 0644
165
+ end
166
+
167
+ context "if any are not readable" do
168
+ before( :each ) do
169
+ @spool_file1.chmod 0222
170
+ end
171
+
172
+ it "should throw an exception" do
173
+ lambda { SpoolPool::Pool.validate_pool_dir( @spool_pathname.to_s ) }.should raise_error
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
182
+
183
+
184
+ describe "#initialize" do
185
+ it "should set the spool_dir attribute" do
186
+ @instance.spool_dir.to_s.should == @spool_path
187
+ end
188
+
189
+ it "should set up the spools attribute" do
190
+ @instance.spools.should == {}
191
+ end
192
+
193
+ context "the spool_dir does not exist" do
194
+ before( :each ) do
195
+ @spool_pathname.rmtree if @spool_pathname.exist?
196
+ end
197
+
198
+ context "and it can create the spool dir" do
199
+ before( :each ) do
200
+ @root_pathname.chmod 0755
201
+ end
202
+
203
+ it "should try to create the spool_dir" do
204
+ SpoolPool::Pool.new( @spool_path )
205
+ @spool_pathname.should exist
206
+ @spool_pathname.unlink if @spool_pathname.exist?
207
+ end
208
+ end
209
+
210
+ context "and it can't create the spool dir" do
211
+ it "should raise an exception" do
212
+ with_fs_mode( @root_pathname, 0555 ) do
213
+ lambda { SpoolPool::Pool.new( @spool_path ) }.should raise_error( Errno::EACCES )
214
+ end
215
+ end
216
+ end
217
+ end
218
+
219
+ context "the spool_dir exists" do
220
+ it "should raise an exception if it can't create a file" do
221
+ with_fs_mode( @spool_pathname, 0555 ) do
222
+ lambda { SpoolPool::Pool.new( @spool_path ) }.should raise_error( Errno::EACCES )
223
+ end
224
+ end
225
+
226
+ it "should raise an exception if it can't read a file" do
227
+ with_fs_mode( @spool_pathname, 0333 ) do
228
+ lambda { SpoolPool::Pool.new( @spool_path ) }.should raise_error( Errno::EACCES )
229
+ end
230
+ end
231
+ end
232
+ end
233
+
234
+ describe "#put" do
235
+ it "should create queue_dir object in the queues attribute if it doesn't exist yet" do
236
+ @instance.spools.delete @spool
237
+ filename = @instance.put( @spool, 'some value' )
238
+ @instance.spools[@spool].should_not be_nil
239
+ File.unlink( filename ) if File.exist?( filename )
240
+ end
241
+
242
+ it "should return the filename of the spool file" do
243
+ filename = @instance.put( @spool, 'some value' )
244
+ filename.should be_a( String )
245
+ Pathname.new( filename ).read.should == SpoolPool::Spool.serialize( 'some value' )
246
+ end
247
+
248
+ context "queue names that try to escape the queue_dir" do
249
+ it "should raise an exception on directory traversal attempts" do
250
+ lambda { @instance.put "../../foo", 'some value' }.should raise_error
251
+ end
252
+
253
+ it "should not raise an exception if following a symlink" do
254
+ real_path = Pathname.new( @spool_pathname + "real" )
255
+ real_path.mkpath
256
+ symlinked_path = Pathname.new( @spool_pathname + "symlink" )
257
+ symlinked_path.make_symlink( real_path.to_s )
258
+
259
+ lambda { @instance.put "symlink", "some value" }.should_not raise_error
260
+
261
+ symlinked_path.unlink
262
+ real_path.rmtree
263
+ end
264
+ end
265
+ end
266
+
267
+ describe "the #get/#put pair" do
268
+ before( :each ) do
269
+ @data = { :foo => "some value", :bar => [ 3, 3.45, "another string" ] }
270
+ @instance.put @spool, @data
271
+ end
272
+
273
+ it "should serialize/deserialize the data written and returned" do
274
+ @instance.get( @spool ).should == @data
275
+ end
276
+
277
+ it "should serialize/deserialize the data written and yielded" do
278
+ @instance.get( @spool ) do |read_data|
279
+ read_data.should == @data
280
+ end
281
+ end
282
+ end
283
+
284
+ describe "#get" do
285
+ it "should yield the contents of one of the files with the oldest spooling time in spool directory" do
286
+ oldest_data = 'foo'
287
+ youngest_data = 'blubb'
288
+ @instance.put @spool, oldest_data
289
+ @instance.put @spool, youngest_data
290
+
291
+ @instance.get( @spool ) { |spool_data| spool_data.should == oldest_data }
292
+ end
293
+
294
+ context "if the spool object doesn't exist yet" do
295
+ context "if such a spool directory exists" do
296
+ before( :each ) do
297
+ @instance.put @spool, @data
298
+ @instance.spools.delete @spool
299
+ end
300
+
301
+ it "should create a spool object" do
302
+ @instance.get( @spool ) {}
303
+ @instance.spools[@spool].should_not be_nil
304
+ end
305
+
306
+ it "should return the result of the get operation" do
307
+ @instance.get( @spool ){ |spool_data| spool_data.should == @data }
308
+ end
309
+ end
310
+
311
+ context "if such a spool directory does not exist" do
312
+ it "should return nil" do
313
+ @instance.get( :non_existant_spool ){}.should be_nil
314
+ end
315
+ end
316
+ end
317
+
318
+ context "no file is available in the requested spool" do
319
+ before( :each ) do
320
+ @spool_pathname.children.each { |child| child.unlink }
321
+ end
322
+
323
+ it "should return nil" do
324
+ @instance.get( @spool ){}.should be_nil
325
+ end
326
+ end
327
+
328
+ it "should raise an exception if the queue directory is not readable" do
329
+ @instance.put @spool, "some data"
330
+
331
+ with_fs_mode( @spool_pathname, 0000 ) do
332
+ lambda { @instance.get( @spool ){} }.should raise_error
333
+ end
334
+ end
335
+
336
+ context "no block was passed" do
337
+ before( :each ) do
338
+ path = @instance.put( @spool, @data )
339
+ @path = Pathname.new( path )
340
+ end
341
+
342
+ it "should delete the read file" do
343
+ @instance.get @spool
344
+ @path.should_not be_exist
345
+ end
346
+
347
+ it "should return the result of the get operation" do
348
+ @instance.get( @spool ).should == @data
349
+ end
350
+ end
351
+
352
+ context "a block was passed" do
353
+ context "if no exception was raised in the block" do
354
+ it "should delete the read file" do
355
+ path = Pathname.new( @instance.put( @spool, @data ) )
356
+ @instance.get( @spool ) {}
357
+ path.should_not be_exist
358
+ end
359
+ end
360
+
361
+ context "if an exception was raised in the block" do
362
+ it "should not delete the read file" do
363
+ path = Pathname.new( @instance.put( @spool, @data ) )
364
+ lambda{ @instance.get( @spool ) {raise RuntimeError} }
365
+ path.should be_exist
366
+ end
367
+
368
+ it "should let the exception bubble up" do
369
+ path = Pathname.new( @instance.put( @spool, @data ) )
370
+ lambda{ @instance.get( @spool ) {raise RuntimeError} }.should raise_error( RuntimeError )
371
+ end
372
+ end
373
+ end
374
+
375
+ it "should raise an exception if the oldest file in the queue directory is not readable" do
376
+ path = Pathname.new( @instance.put( @spool, @data ) )
377
+ with_fs_mode( path, 0333 ) do
378
+ lambda { @instance.get( @spool ){} }.should raise_error
379
+ end
380
+ path.unlink
381
+ end
382
+
383
+ it "should raise an exception if the oldest file in the queue directory is not deleteable" do
384
+ path = Pathname.new( @instance.put( @spool, @data ) )
385
+ with_fs_mode( @instance.spools[@spool].pathname, 0555 ) do
386
+ lambda { @instance.get( @spool ){} }.should raise_error
387
+ end
388
+ path.unlink
389
+ end
390
+
391
+ context "queue names that try to escape the queue_dir" do
392
+ it "should raise an exception on directory traversal attempts" do
393
+ lambda { @instance.get( "../../foo" ){} }.should raise_error
394
+ end
395
+
396
+ it "should not raise an exception if following a symlink" do
397
+ real_path = Pathname.new( @spool_pathname + "real" )
398
+ real_path.mkpath
399
+ symlinked_path = Pathname.new( @spool_pathname + "symlink" )
400
+ symlinked_path.make_symlink( real_path.to_s )
401
+
402
+ lambda { @instance.get( "symlink" ){} }.should_not raise_error
403
+
404
+ symlinked_path.unlink
405
+ real_path.rmtree
406
+ end
407
+ end
408
+ end
409
+
410
+ describe "#flush" do
411
+ context "each file in the spool directory" do
412
+ before( :each ) do
413
+ @oldest_data = "oldest data"
414
+ @middle_data = "middle data"
415
+ @youngest_data = "youngest data"
416
+ @oldest_file = @instance.put @spool, @oldest_data
417
+ @middle_file = @instance.put @spool, @middle_data
418
+ @youngest_file = @instance.put @spool, @youngest_data
419
+ end
420
+
421
+ it "should be yielded to the passed block" do
422
+ times_yielded = 0
423
+ @instance.flush( @spool ) { times_yielded += 1 }
424
+ times_yielded.should == 3
425
+ end
426
+
427
+ it "should be yielded ordered by date, oldest first" do
428
+ times_yielded = 0
429
+ @instance.flush( @spool ) do |data|
430
+ times_yielded += 1
431
+ case times_yielded
432
+ when 1 then data.should == @oldest_data
433
+ when 2 then data.should == @middle_data
434
+ when 3 then data.should == @youngest_data
435
+ end
436
+ end
437
+ end
438
+
439
+ it "should be deleted after it was processed" do
440
+ times_yielded = 0
441
+ @instance.flush( @spool ) do |data|
442
+ times_yielded += 1
443
+ case times_yielded
444
+ when 1 then File.exist?(@oldest_file).should_not be_true
445
+ when 2 then File.exist?(@middle_file).should_not be_true
446
+ when 3 then File.exist?(@youngest_file).should_not be_true
447
+ end
448
+ end
449
+ end
450
+ end
451
+
452
+ it "should raise an exception if the queue directory is not readable" do
453
+ @instance.put( @spool, @data )
454
+ with_fs_mode( @instance.spools[@spool].pathname, 0000 ) do
455
+ lambda { @instance.flush( @spool ) }.should raise_error
456
+ end
457
+ end
458
+
459
+ it "should raise an exception if the oldest file in the queue directory is not readable" do
460
+ path = Pathname.new( @instance.put( @spool, @data ) )
461
+ with_fs_mode( path, 0333 ) do
462
+ lambda { @instance.flush( @spool ) }.should raise_error
463
+ end
464
+ path.unlink
465
+ end
466
+
467
+ it "should raise an exception if the oldest file in the queue directory is not deleteable" do
468
+ path = Pathname.new( @instance.put( @spool, @data ) )
469
+ with_fs_mode( @instance.spools[@spool].pathname, 0555 ) do
470
+ lambda { @instance.flush( @spool ) {} }.should raise_error
471
+ end
472
+ end
473
+
474
+ context "queue names that try to escape the queue_dir" do
475
+ it "should raise an exception on directory traversal attempts" do
476
+ lambda { @instance.flush( "../../foo" ) {} }.should raise_error
477
+ end
478
+
479
+ it "should not raise an exception if following a symlink" do
480
+ real_path = Pathname.new( @spool_pathname + "real" )
481
+ real_path.mkpath
482
+ symlinked_path = Pathname.new( @spool_pathname + "symlink" )
483
+ symlinked_path.make_symlink( real_path.to_s )
484
+
485
+ lambda { @instance.flush( "symlink" ) {} }.should_not raise_error
486
+
487
+ symlinked_path.unlink
488
+ real_path.rmtree
489
+ end
490
+ end
491
+ end
492
+ end