waitfor 0.1.0 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,32 @@
1
+ === 0.1.7 2011-04-19
2
+
3
+ * Bug fix related to not accumulating seconds when minutes was specified
4
+ * Enhanced rspec tests
5
+
6
+ === 0.1.6 2011-03-23
7
+
8
+ * Various bug fixes.
9
+
10
+ === 0.1.5 2011-03-23
11
+
12
+ * Additional refactoring to separate out the option parsing in preparation for new functionality
13
+
14
+ === 0.1.3 2010-11-14
15
+
16
+ * Minor refactoring and enhancement of tests
17
+
18
+ === 0.1.2 2010-11-13
19
+
20
+ * Updated tests to use mocks to increase their speed dramatically
21
+
22
+ === 0.1.1 2010-02-05
23
+
24
+ * Updated tests to use standard benchmarking library
25
+
26
+ === 0.1.0 2010-02-03
27
+
28
+ * Added new time formats, custom exceptions and messages
29
+
1
30
  === 0.0.1 2010-01-24
2
31
 
3
- * 1 major enhancement:
4
- * Initial release
32
+ * Initial release
@@ -1,16 +1,22 @@
1
1
  History.txt
2
2
  Manifest.txt
3
- README.rdoc
4
3
  Rakefile
4
+ README.rdoc
5
5
  lib/waitfor.rb
6
6
  lib/waitfor/fixnum.rb
7
+ lib/waitfor/timeout_error.rb
7
8
  lib/waitfor/timer.rb
8
9
  lib/waitfor/version.rb
9
- lib/waitfor/waitfor_timeout_error.rb
10
+ lib/waitfor/settings/configuration.rb
11
+ lib/waitfor/settings/legacy_parser.rb
12
+ lib/waitfor/settings/parser.rb
13
+ spec/configuration_spec.rb
10
14
  spec/fixnum_spec.rb
15
+ spec/legacy_parser_spec.rb
16
+ spec/parser_spec.rb
11
17
  spec/spec.opts
12
18
  spec/spec_helper.rb
13
19
  spec/spec_helper_spec.rb
14
- spec/waitfor_spec.rb
20
+ spec/timeout_error_spec.rb
15
21
  spec/timer_spec.rb
16
- spec/waitfor_timeout_error_spec.rb
22
+ spec/waitfor_spec.rb
@@ -4,44 +4,62 @@
4
4
 
5
5
  == DESCRIPTION:
6
6
 
7
- Blocks execution until a specified statement becomes true, or a specified time interval is reached, at which point an error is raised.
7
+ Simple solution to the sleep( ) test anti-pattern.
8
8
 
9
- Useful for strengthening tests involving asynchronous or non-blocking operations.
9
+ Blocks execution until a supplied block returns true, or a specified time interval is reached, at which point an error is raised.
10
10
 
11
- == FEATURES/PROBLEMS:
11
+ object.some_nonblocking_operation
12
+
13
+ WaitFor.upto 30.seconds do
14
+ object.completed?
15
+ end
16
+
17
+ == FEATURES:
12
18
 
13
19
  * Accepts time intervals in two formats
14
- * Simple time DSL of seconds or minutes
15
- * 30.seconds
16
- * 5.minutes
17
20
  * Hash with symbols :seconds or :minutes
18
21
  * :seconds => 30
19
22
  * :minutes => 5
20
23
  * Singular forms ( 1.second or :minute => 1 ) accepted as well
24
+ * Legacy mode with seconds or minutes
25
+ * 30.seconds
26
+ * 5.minutes
21
27
  * Allows for custom exceptions, custom error messages or both
22
28
  * For custom exceptions, add ':exception => YourException' to the options
23
29
  * Use either :exception or :error for custom exceptions ( both are identical in functionality )
24
30
  * For custom messages, add ':message => "Your Custom Message"' to the options
25
31
 
26
- * Resolution of delay between Ruby block executions is currently 1 second, not yet configurable
27
- * Must use hash if specifying custom exceptions or messages, can't mix and match simple time DSL with hash ( yet )
32
+ * Resolution of delay between Ruby block executions is currently 1 second ( and not yet configurable )
33
+ * Must use hash if specifying custom exceptions or messages, can't mix and match simple time DSL with hash
28
34
 
29
35
  == SYNOPSIS:
30
36
 
31
- This gem is used in cases where the execution of an operation occurs asynchronously but a method to determine the operation's status exists. In these cases
32
- you want to 'wait for' that operation to either complete by continuously checking the status or fail at a specified timeout.
37
+ Simple solution to the sleep( ) test anti-pattern.
33
38
 
34
- The example below runs a non-blocking operation which can not return whether it succeeded or failed. WaitFor blocks execution, in this case, for up to 30 seconds
35
- while it continuously runs a Ruby block supplied by the user. The Ruby block in this example determines whether the previous non-blocking operation has run to completion.
36
- As the Ruby block returns false, the timer continues to count down. If it returns true, then it breaks out of the loop and continues on its way. However, if the time elapsed
37
- becomes greater than the time permitted, 30 seconds in this example, then an exception is raised.
39
+ Blocks execution until a supplied block returns true, or a specified time interval is reached, at which point an error is raised.
38
40
 
39
- object.some_nonblocking_operation
41
+ Useful for adding elasticity to tests involving asynchronous or non-blocking operations. Instead of sleep( )'ing in a test, add a WaitFor.
42
+ Reduces test execution time by waiting only as long as required. Adds robustness by giving test steps enough time to execute, reducing
43
+ fail-positive failures from timeouts.
40
44
 
41
- WaitFor.upto 30.seconds do
45
+ Whereas you might normally do something like this:
46
+
47
+ object.some_asynchronous_operation
48
+
49
+ # Wait for it to finish and let's hope it doesn't take 31 seconds, because then the test might fail without good reason!
50
+ # Let's also hope it takes no less than 29 seconds, because then we're making the test slow!
51
+ sleep( 30 )
52
+
53
+ It would be far superior to use WaitFor in this manner:
54
+
55
+ object.some_asynchronous_operation
56
+
57
+ WaitFor.upto 60.seconds do
42
58
  object.completed?
43
59
  end
44
60
 
61
+ Here the test could take as long as 60 seconds and still pass, likewise, if it took only 1 second, it would continue on with minimal delay. We've removed the brittleness that sleep() adds.
62
+
45
63
  Custom exception and message are specified in the examples below.
46
64
 
47
65
  WaitFor.upto( :minute => 1, :exception => EventFailedToOccurError ) do
@@ -58,17 +76,16 @@ Custom exception and message are specified in the examples below.
58
76
 
59
77
  Finally, a few more "real world" examples.
60
78
 
61
- button.click
79
+ page.click "Link"
62
80
 
63
- WaitFor.upto( :minute => 1, :message => "New screen did not appear within 1 minute after clicking button" ) do
64
- Check.whether( new_screen ).is :visible
81
+ WaitFor.upto( :minute => 1, :message => "New page did not appear within 1 minute after clicking link" ) do
82
+ page.has_loaded? && page.title == "Home"
65
83
  end
66
84
 
67
- website_cluster.propagate_new_entry "Good news everyone!"
68
85
 
69
- WaitFor.upto( :seconds => 30, :exception => WebsiteFailedToUpdateError ) do
70
- website_cluster.each_node_reports "Good news everyone!"
71
- end
86
+ print "/tmp/file"
87
+
88
+ WaitFor.upto( 30.seconds ) { files_in_queue( "/tmp/print/queue" ).count == 1 }
72
89
 
73
90
  == REQUIREMENTS:
74
91
 
@@ -82,7 +99,7 @@ Finally, a few more "real world" examples.
82
99
 
83
100
  (The MIT License)
84
101
 
85
- Copyright (c) 2010 James Bobowski
102
+ Copyright (c) 2010-2011 James Bobowski
86
103
 
87
104
  Permission is hereby granted, free of charge, to any person obtaining
88
105
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -9,10 +9,10 @@ Hoe.plugin :newgem
9
9
  # Generate all the Rake tasks
10
10
  # Run 'rake -T' to see list of generated tasks (from gem root directory)
11
11
  $hoe = Hoe.spec 'waitfor' do
12
- self.developer 'James Bobowski', 'james.bobowski@gmail.com'
13
- self.rubyforge_name = self.name
14
- self.version = WaitFor::VERSION::STRING
15
- self.description = "Blocks execution until a specified statement becomes true, or a specified time interval is reached, at which point an error is raised."
12
+ self.developer 'James Bobowski', 'james.bobowski@gmail.com'
13
+ self.rubyforge_name = self.name
14
+ self.version = WaitFor::VERSION::STRING
15
+ self.description = "Blocks execution until a specified statement becomes true, or a specified time interval is reached, at which point an error is raised."
16
16
  end
17
17
 
18
18
  require 'newgem/tasks'
@@ -1,16 +1,40 @@
1
+ # @author James Bobowski
2
+
1
3
  $:.unshift( File.dirname( __FILE__ ) ) unless
2
4
  $:.include?( File.dirname( __FILE__ ) ) || $:.include?( File.expand_path( File.dirname( __FILE__ ) ) )
3
5
 
4
6
  require 'waitfor/fixnum'
7
+ require 'waitfor/timeout_error'
5
8
  require 'waitfor/timer'
9
+ require 'waitfor/settings/configuration'
10
+ require 'waitfor/settings/legacy_parser'
11
+ require 'waitfor/settings/parser'
6
12
 
7
13
  module WaitFor
8
14
  class << self
15
+ # Execute the given block until it returns true or the specified timeout is reached.
16
+ #
17
+ # @param [Hash] options the options for upon( )
18
+ # @option options [Fixnum] :seconds Timeout in seconds
19
+ # @option options [String] :minutes Timeout in minutes
20
+ # @option options [String] :exception Exception to throw if timeout is reached
21
+ # @option options [String] :message Custom message for exception if timeout is reached
22
+ # @yield Block to execute until it returns true
23
+ #
24
+ # @example
25
+ # WaitFor.upto( 30.seconds ) do
26
+ # has_event_happened?
27
+ # end
28
+ #
29
+ # @example
30
+ # WaitFor.upto( :minute => 1, :seconds => 30 ) do
31
+ # has_event_happened?
32
+ # end
9
33
  def upto( options )
10
34
  timer = Timer.new options
11
35
  until yield or timer.out_of_time?
12
- sleep 1
36
+ Kernel.sleep 1
13
37
  end
14
38
  end
15
39
  end
16
- end
40
+ end
@@ -1,14 +1,30 @@
1
- class Fixnum #:nodoc:
2
- def second
3
- self.seconds
1
+ begin
2
+ # Attempt to access the Rails version because this will work in a Rails environment.
3
+ Rails.version
4
+ # If NameError is thrown, then we know we aren't in a Rails environment.
5
+ rescue NameError
6
+ # Since we aren't in a Rails environment, we'll need to monkeypatch Fixnum to support .seconds and .minutes
7
+ class Fixnum
8
+ # Return the current number unmodified since it is already in seconds.
9
+ #
10
+ # @return [Fixnum] the current unmodified.
11
+ def seconds
12
+ self
13
+ end
14
+
15
+ # Convert the current number into seconds.
16
+ #
17
+ # @return [Fixnum] the current number as seconds.
18
+ def minutes
19
+ self * 60
20
+ end
21
+
22
+ # Alias for seconds.
23
+ #
24
+ alias :second :seconds
25
+
26
+ # Alias for minutes.
27
+ #
28
+ alias :minute :minutes
4
29
  end
5
- def seconds
6
- self
7
- end
8
- def minute
9
- self.minutes
10
- end
11
- def minutes
12
- self * 60
13
- end
14
- end
30
+ end
@@ -0,0 +1,35 @@
1
+ # @author James Bobowski
2
+ module WaitFor
3
+ module Settings
4
+ class Configuration
5
+
6
+ attr_accessor :seconds, :exception, :message
7
+
8
+ # A new instance of Configuration.
9
+ #
10
+ # @param [Hash|Fixnum] options settings for configuration such as `:seconds`, `:minutes`, `:exception` and `:message` or just a Fixnum representing seconds
11
+ def initialize( options )
12
+ # Initialize seconds to zero, so that from there we can add seconds to it.
13
+ @seconds = 0
14
+ # Determine which parser should be used, then call it, passing ourselves in for assignment of the parsed values.
15
+ parser_factory( options ).parse( options, self )
16
+ end
17
+
18
+ private
19
+
20
+ # Determine which parser should be used based on the type of object passed in.
21
+ #
22
+ # @param [Hash|Fixnum] options settings for Configuration
23
+ # @return [Class] class of parser to use for this type of settings object
24
+ def parser_factory( options )
25
+ if options.instance_of? Fixnum
26
+ WaitFor::Settings::LegacyParser
27
+ elsif options.instance_of? Hash
28
+ WaitFor::Settings::Parser
29
+ else
30
+ raise "Unable to parse configuration options."
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,44 @@
1
+ # @author James Bobowski
2
+ module WaitFor
3
+ module Settings
4
+ class LegacyParser
5
+
6
+ attr_accessor :params, :config
7
+
8
+ # Parse parameters and return Configuration object.
9
+ #
10
+ # @param [Fixnum] params parameters
11
+ # @param [Configuration] config new Configuration object to populate with options
12
+ # @return [Configuration] the configured Configuration object
13
+ def self.parse( params, config )
14
+ parser = WaitFor::Settings::LegacyParser.new( params, config )
15
+ parser.parse
16
+ parser.config
17
+ end
18
+
19
+ # Create a new Parser.
20
+ #
21
+ # @param [Fixnum] params parameters
22
+ # @param [Configuration] config new Configuration object to populate with options
23
+ # @return [Configuration] the configured Configuration object
24
+ def initialize( params, config )
25
+ @params, @config = params, config
26
+
27
+ # Default exception since legacy does not support specifying an exception.
28
+ @config.exception = WaitFor::TimeoutError
29
+ end
30
+
31
+ # Call all parsing methods in order.
32
+ def parse
33
+ parse_time
34
+ end
35
+
36
+ private
37
+
38
+ # Parse seconds or minutes and set the Configuration's object seconds attribute.
39
+ def parse_time
40
+ @config.seconds = @params
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,59 @@
1
+ # @author James Bobowski
2
+ module WaitFor
3
+ module Settings
4
+ class Parser
5
+
6
+ attr_accessor :params, :config
7
+
8
+ # Parse parameters and return Configuration object.
9
+ #
10
+ # @param [Fixnum] params parameters
11
+ # @param [Configuration] config new Configuration object to populate with options
12
+ # @return [Configuration] the configured Configuration object
13
+ def self.parse( params, config )
14
+ parser = WaitFor::Settings::Parser.new( params, config )
15
+ parser.parse
16
+ parser.config
17
+ end
18
+
19
+ # Create a new Parser.
20
+ #
21
+ # @param [Fixnum] params parameters
22
+ # @param [Configuration] config new Configuration object to populate with options
23
+ # @return [Configuration] the configured Configuration object
24
+ def initialize( params, config )
25
+ @params, @config = params, config
26
+ end
27
+
28
+ # Call all parsing methods in order.
29
+ def parse
30
+ parse_seconds
31
+ parse_minutes
32
+ parse_exception
33
+ parse_message
34
+ end
35
+
36
+ private
37
+
38
+ # Parse seconds and add to the Configuration's object seconds attribute.
39
+ def parse_seconds
40
+ @config.seconds += ( @params[ :second ] || 0 ) + ( @params[ :seconds ] || 0 )
41
+ end
42
+
43
+ # Parse minutes and add to the Configuration's object seconds attribute.
44
+ def parse_minutes
45
+ @config.seconds += ( ( @params[ :minute ] || 0 ) + ( @params[ :minutes ] || 0 ) ) * 60
46
+ end
47
+
48
+ # Parse exception and set the Configuration's object exception attribute.
49
+ def parse_exception
50
+ @config.exception = @params[ :exception ] || WaitFor::TimeoutError
51
+ end
52
+
53
+ # Parse message and set the Configuration's object message attribute.
54
+ def parse_message
55
+ @config.message = @params[ :message ] || "WaitFor timed out!"
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,4 @@
1
+ # @author James Bobowski
2
+ module WaitFor
3
+ class TimeoutError < RuntimeError; end;
4
+ end
@@ -1,35 +1,44 @@
1
- class Timer #:nodoc:
2
- def initialize( options )
3
- @seconds = Timer.to_seconds options
4
-
5
- if options.instance_of? Hash
6
- @exception = options[ :exception ]
7
- @exception = options[ :error ] || @exception
8
- @message = options[ :message ]
1
+ # @author James Bobowski
2
+ module WaitFor
3
+ class Timer
4
+ # A new instance of Timer.
5
+ #
6
+ # @param [Hash] options for Timer configuration such as `:seconds`, `:minutes`, `:exception` and `:message`
7
+ def initialize( options )
8
+ @config = WaitFor::Settings::Configuration.new( options )
9
+ @start_time = Time.now
9
10
  end
10
11
 
11
- @exception ||= WaitFor::TimeoutError
12
- @start_time = Time.now
13
- end
14
-
15
- def self.to_seconds( options )
16
- # Not a hash, already converted to seconds, return as is
17
- return options if options.instance_of? Fixnum
18
-
19
- seconds = options[ :second ] || 0
20
- seconds = options[ :seconds ] || seconds
12
+ # Returns whether the elapsed time has surpassed the allotted time
13
+ #
14
+ # @return [Boolean] of elapsed time is greater or equal to the set number of seconds
15
+ # @raise [WaitFor::TimeoutError] Timeout error if time limit is reached
16
+ def out_of_time?
17
+ raise_exception if timed_out?
18
+ false
19
+ end
21
20
 
22
- minutes = options[ :minute ] || 0
23
- minutes = options[ :minutes ] || minutes
21
+ private
24
22
 
25
- return seconds + minutes * 60
26
- end
23
+ # Determine whether the amount of time elapsed is greater than the limit.
24
+ #
25
+ # @return [Boolean] whether the amount of time elapsed is greater than the limit.
26
+ def timed_out?
27
+ elapsed_time >= @config.seconds
28
+ end
27
29
 
28
- def out_of_time?
29
- if ( Time.now - @start_time ) > @seconds
30
- raise @exception, @message
30
+ # Calculate the number of seconds that have passed since object was initialized.
31
+ #
32
+ # @return [Fixnum] number seconds that have passed since object was initialized.
33
+ def elapsed_time
34
+ Time.now - @start_time
31
35
  end
32
36
 
33
- false
37
+ # Raise the exceptions for this object.
38
+ #
39
+ # @raise [WaitFor::TimeoutError] Timeout error
40
+ def raise_exception
41
+ raise @config.exception, @config.message
42
+ end
34
43
  end
35
- end
44
+ end