spool_pool 0.2.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.
- data/History.rdoc +15 -0
- data/LICENSE.txt +27 -0
- data/README.rdoc +53 -0
- data/TODOs +1 -0
- data/lib/spool_pool.rb +53 -0
- data/lib/spool_pool/file.rb +161 -0
- data/lib/spool_pool/pool.rb +172 -0
- data/lib/spool_pool/spool.rb +113 -0
- data/scripts/perf_test.rb +16 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/spool_pool/file_spec.rb +119 -0
- data/spec/spool_pool/pool_spec.rb +492 -0
- data/spec/spool_pool/spool_spec.rb +245 -0
- metadata +68 -0
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|