/spec/thingfish/mixins_spec.rb
Ruby | 360 lines | 255 code | 101 blank | 4 comment | 46 complexity | 7c2b5e8cb619fbd5488a502b4520b7d9 MD5 | raw file
Possible License(s): BSD-3-Clause
1#!/usr/bin/env ruby
2
3BEGIN {
4 require 'pathname'
5 basedir = Pathname.new( __FILE__ ).dirname.parent.parent
6
7 libdir = basedir + "lib"
8
9 $LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
10 $LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
11}
12
13require 'rspec'
14require 'stringio'
15
16require 'spec/lib/helpers'
17
18require 'thingfish/mixins'
19require 'thingfish/handler'
20require 'thingfish/testconstants'
21
22
23#####################################################################
24### C O N T E X T S
25#####################################################################
26
27describe ThingFish::Loggable do
28
29 context "including class" do
30 before(:each) do
31 @logfile = StringIO.new('')
32 ThingFish.logger = Logger.new( @logfile )
33
34 @test_class = Class.new do
35 include ThingFish::Loggable
36
37 def log_test_message( level, msg )
38 self.log.send( level, msg )
39 end
40
41 def logdebug_test_message( msg )
42 self.log_debug.debug( msg )
43 end
44 end
45 @obj = @test_class.new
46 end
47
48
49 it "is able to output to the log via its #log method" do
50 @obj.log_test_message( :debug, "debugging message" )
51 @logfile.rewind
52 @logfile.read.should =~ /debugging message/
53 end
54
55 it "is able to output to the log via its #log_debug method" do
56 @obj.logdebug_test_message( "sexydrownwatch" )
57 @logfile.rewind
58 @logfile.read.should =~ /sexydrownwatch/
59 end
60 end
61
62
63 context "including class with custom formats" do
64 before(:each) do
65 @logfile = StringIO.new('')
66 @logger = ThingFish.logger = Logger.new( @logfile )
67
68 formatter = ThingFish::LogFormatter.new( @logger, '%7$s', 'D: %7$s' )
69 @logger.formatter = formatter
70
71 @test_class = Class.new do
72 include ThingFish::Loggable
73
74 def log_test_message( level, msg )
75 self.log.send( level, msg )
76 end
77
78 def logdebug_test_message( msg )
79 self.log_debug.debug( msg )
80 end
81 end
82 @obj = @test_class.new
83 end
84
85
86 it "is able to output to the log via its #log method" do
87 @logger.should_receive( :level ).and_return( Logger::INFO )
88 @obj.log_test_message( :info, 'scoby pancakes' )
89 @logfile.rewind
90 @logfile.read.should == 'scoby pancakes'
91 end
92
93 it "is able to output to the log via its #log_debug method" do
94 @obj.logdebug_test_message( 'poop tubes' )
95 @logfile.rewind
96 @logfile.read.should == 'D: poop tubes'
97 end
98 end
99
100end
101
102describe ThingFish::StaticResourcesHandler do
103
104 context "mixed into a class" do
105 before(:each) do
106 @test_class = Class.new( ThingFish::Handler ) do
107 include ThingFish::StaticResourcesHandler
108 end
109 end
110
111
112 it "has a 'static_resources_dir' class method" do
113 @test_class.should respond_to( :static_resources_dir)
114 end
115
116 it "has a 'static_resources_dir' of 'static' by default" do
117 @test_class.static_resources_dir.should == 'static'
118 end
119 end
120
121 context "mixed into a handler class that has set the static resources dir" do
122 before(:each) do
123 @test_class = Class.new( ThingFish::Handler ) do
124 include ThingFish::StaticResourcesHandler
125 static_resources_dir "static-content"
126 end
127 end
128
129
130 it "should have the specified static_resources_dir" do
131 @test_class.static_resources_dir.should == 'static-content'
132 end
133 end
134
135 context "mixed into an instance of a handler class" do
136
137 before(:each) do
138 @test_class = Class.new( ThingFish::Handler ) do
139 include ThingFish::StaticResourcesHandler
140 static_resources_dir "static-content"
141 end
142 @test_handler = @test_class.new( '/glah' )
143 end
144
145
146 it "registers another handler at its own location when registered" do
147 daemon = mock( "daemon" ).as_null_object
148 urimap = mock( "urimap" )
149 daemon.stub!( :urimap ).and_return( urimap )
150 urimap.should_receive( :register_first ).with( '/glah', duck_type(:on_startup, :process) )
151
152 @test_handler.on_startup( daemon )
153 end
154 end
155
156end
157
158describe ThingFish::ResourceLoader do
159
160 it "adds a #get_resource method to including classes" do
161 klass = Class.new { include ThingFish::ResourceLoader }
162 obj = klass.new
163 obj.should respond_to( :get_resource )
164 end
165
166
167 context "mixed into a class" do
168
169 before( :all ) do
170 ThingFish.reset_logger
171 ThingFish.logger.level = Logger::FATAL
172
173 @resdir = make_tempdir()
174 @resdir.mkpath
175
176 @tmpfile = Tempfile.new( 'test.txt', @resdir )
177 @tmpfile.print( TEST_RESOURCE_CONTENT )
178 @tmpfile.close
179 @tmpname = Pathname.new( @tmpfile.path ).basename
180
181 @klass = Class.new {
182 include ThingFish::ResourceLoader
183 public :get_resource, :resource_exists?, :resource_directory?
184 def initialize( resdir )
185 @resource_dir = resdir
186 end
187 }
188 end
189
190 after( :all ) do
191 @resdir.rmtree
192 ThingFish.reset_logger
193 end
194
195 before( :each ) do
196 @obj = @klass.new( @resdir )
197 end
198
199 it "should know what its resource directory is" do
200 @obj.resource_dir.should == @resdir
201 end
202
203 it "is able to load stuff from its resources dir" do
204 @obj.get_resource( @tmpname ).should == TEST_RESOURCE_CONTENT
205 end
206
207 it "can test for the existance of a resource" do
208 @obj.resource_exists?( @tmpname ).should be_true()
209 end
210
211 it "can test for the existance of a resource directory" do
212 @obj.resource_directory?( @tmpname ).should be_false()
213 dir = (@resdir + 'testdirectory')
214 @obj.resource_directory?( dir.basename ).should be_false()
215 dir.mkpath
216 @obj.resource_directory?( dir.basename ).should be_true()
217 end
218
219 end
220
221end
222
223
224describe ThingFish::AbstractClass do
225
226 context "mixed into a class" do
227 it "will cause the including class to hide its ::new method" do
228 testclass = Class.new { include ThingFish::AbstractClass }
229
230 expect {
231 testclass.new
232 }.to raise_error( NoMethodError, /private/ )
233 end
234
235 end
236
237
238 context "mixed into a superclass" do
239
240 before(:each) do
241 testclass = Class.new {
242 include ThingFish::AbstractClass
243 virtual :test_method
244 }
245 subclass = Class.new( testclass )
246 @instance = subclass.new
247 end
248
249
250 it "raises a NotImplementedError when unimplemented API methods are called" do
251 expect {
252 @instance.test_method
253 }.to raise_error( NotImplementedError, /does not provide an implementation of/ )
254 end
255
256 it "declares the virtual methods so that they can be used with arguments under Ruby 1.9" do
257 expect {
258 @instance.test_method( :some, :arguments )
259 }.to raise_error( NotImplementedError, /does not provide an implementation of/ )
260 end
261
262 end
263
264end
265
266
267describe ThingFish::NumericConstantMethods do
268
269 context "mixed into Numerics" do
270
271 SECONDS_IN_A_MINUTE = 60
272 SECONDS_IN_AN_HOUR = SECONDS_IN_A_MINUTE * 60
273 SECONDS_IN_A_DAY = SECONDS_IN_AN_HOUR * 24
274 SECONDS_IN_A_WEEK = SECONDS_IN_A_DAY * 7
275 SECONDS_IN_A_FORTNIGHT = SECONDS_IN_A_WEEK * 2
276 SECONDS_IN_A_MONTH = SECONDS_IN_A_DAY * 30
277 SECONDS_IN_A_YEAR = Integer( SECONDS_IN_A_DAY * 365.25 )
278
279 it "can calculate the number of seconds for various units of time" do
280 1.second.should == 1
281 14.seconds.should == 14
282
283 1.minute.should == SECONDS_IN_A_MINUTE
284 18.minutes.should == SECONDS_IN_A_MINUTE * 18
285
286 1.hour.should == SECONDS_IN_AN_HOUR
287 723.hours.should == SECONDS_IN_AN_HOUR * 723
288
289 1.day.should == SECONDS_IN_A_DAY
290 3.days.should == SECONDS_IN_A_DAY * 3
291
292 1.week.should == SECONDS_IN_A_WEEK
293 28.weeks.should == SECONDS_IN_A_WEEK * 28
294
295 1.fortnight.should == SECONDS_IN_A_FORTNIGHT
296 31.fortnights.should == SECONDS_IN_A_FORTNIGHT * 31
297
298 1.month.should == SECONDS_IN_A_MONTH
299 67.months.should == SECONDS_IN_A_MONTH * 67
300
301 1.year.should == SECONDS_IN_A_YEAR
302 13.years.should == SECONDS_IN_A_YEAR * 13
303 end
304
305
306 it "can calulate various time offsets" do
307 starttime = Time.now
308
309 1.second.after( starttime ).should == starttime + 1
310 18.seconds.from_now.should be_within( 10.seconds ).of( starttime + 18 )
311
312 1.second.before( starttime ).should == starttime - 1
313 3.hours.ago.should be_within( 10.seconds ).of( starttime - 10800 )
314 end
315
316
317
318 BYTES_IN_A_KILOBYTE = 1024
319 BYTES_IN_A_MEGABYTE = BYTES_IN_A_KILOBYTE * 1024
320 BYTES_IN_A_GIGABYTE = BYTES_IN_A_MEGABYTE * 1024
321 BYTES_IN_A_TERABYTE = BYTES_IN_A_GIGABYTE * 1024
322 BYTES_IN_A_PETABYTE = BYTES_IN_A_TERABYTE * 1024
323 BYTES_IN_AN_EXABYTE = BYTES_IN_A_PETABYTE * 1024
324
325 it "can calulate the number of bytes for various data sizes" do
326 1.byte.should == 1
327 4.bytes.should == 4
328
329 1.kilobyte.should == BYTES_IN_A_KILOBYTE
330 22.kilobytes.should == BYTES_IN_A_KILOBYTE * 22
331
332 1.megabyte.should == BYTES_IN_A_MEGABYTE
333 116.megabytes.should == BYTES_IN_A_MEGABYTE * 116
334
335 1.gigabyte.should == BYTES_IN_A_GIGABYTE
336 14.gigabytes.should == BYTES_IN_A_GIGABYTE * 14
337
338 1.terabyte.should == BYTES_IN_A_TERABYTE
339 88.terabytes.should == BYTES_IN_A_TERABYTE * 88
340
341 1.petabyte.should == BYTES_IN_A_PETABYTE
342 34.petabytes.should == BYTES_IN_A_PETABYTE * 34
343
344 1.exabyte.should == BYTES_IN_AN_EXABYTE
345 6.exabytes.should == BYTES_IN_AN_EXABYTE * 6
346 end
347
348
349 it "can display integers as human readable filesize values" do
350 234.size_suffix.should == "234b"
351 3492.size_suffix.should == "3.4K"
352 3492425.size_suffix.should == "3.3M"
353 9833492425.size_suffix.should == "9.2G"
354 end
355
356 end
357
358end
359
360