/spec/thingfish/mixins_spec.rb

https://bitbucket.org/laika/thingfish · Ruby · 360 lines · 255 code · 101 blank · 4 comment · 46 complexity · 7c2b5e8cb619fbd5488a502b4520b7d9 MD5 · raw file

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