vapir-common 1.7.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,141 @@
1
+ require 'vapir-common/exceptions'
2
+
3
+ module Vapir
4
+
5
+ def wait_until(*args)
6
+ Waiter.wait_until(*args) {yield}
7
+ end
8
+
9
+ class TimeKeeper
10
+ attr_reader :sleep_time
11
+ def initialize
12
+ @sleep_time = 0.0
13
+ end
14
+ def sleep seconds
15
+ @sleep_time += Kernel.sleep seconds
16
+ end
17
+ def now
18
+ Time.now
19
+ end
20
+ end
21
+
22
+ class Waiter
23
+ # This is an interface to a TimeKeeper which proxies
24
+ # calls to "sleep" and "Time.now".
25
+ # Useful for unit testing Waiter.
26
+ attr_accessor :timer
27
+
28
+ # How long to wait between each iteration through the wait_until
29
+ # loop. In seconds.
30
+ attr_accessor :polling_interval
31
+
32
+ # Timeout for wait_until.
33
+ attr_accessor :timeout
34
+
35
+ @@default_polling_interval = 0.5
36
+ @@default_timeout = 60.0
37
+
38
+ def initialize(timeout=@@default_timeout,
39
+ polling_interval=@@default_polling_interval)
40
+ @timeout = timeout
41
+ @polling_interval = polling_interval
42
+ @timer = TimeKeeper.new
43
+ end
44
+
45
+ # Execute the provided block until either (1) it returns true, or
46
+ # (2) the timeout (in seconds) has been reached. If the timeout is reached,
47
+ # a TimeOutException will be raised. The block will always
48
+ # execute at least once.
49
+ #
50
+ # waiter = Waiter.new(5)
51
+ # waiter.wait_until {puts 'hello'}
52
+ #
53
+ # This code will print out "hello" for five seconds, and then raise a
54
+ # Vapir::TimeOutException.
55
+ def wait_until # block
56
+ start_time = now
57
+ until yield do
58
+ if (duration = now - start_time) > @timeout
59
+ raise Vapir::Exception::TimeOutException.new(duration, @timeout),
60
+ "Timed out after #{duration} seconds."
61
+ end
62
+ sleep @polling_interval
63
+ end
64
+ end
65
+
66
+ # Execute the provided block until either (1) it returns true, or
67
+ # (2) the timeout (in seconds) has been reached. If the timeout is reached,
68
+ # a TimeOutException will be raised. The block will always
69
+ # execute at least once.
70
+ #
71
+ # Waiter.wait_until(5) {puts 'hello'}
72
+ #
73
+ # This code will print out "hello" for five seconds, and then raise a
74
+ # Vapir::TimeOutException.
75
+
76
+ # IDEA: wait_until: remove defaults from Waiter.wait_until
77
+ def self.wait_until(timeout=@@default_timeout,
78
+ polling_interval=@@default_polling_interval)
79
+ waiter = new(timeout, polling_interval)
80
+ waiter.wait_until { yield }
81
+ end
82
+
83
+ private
84
+ def sleep seconds
85
+ @timer.sleep seconds
86
+ end
87
+ def now
88
+ @timer.now
89
+ end
90
+ end
91
+
92
+ end # module
93
+
94
+ require 'vapir-common/handle_options'
95
+
96
+ class WaiterError < StandardError; end
97
+ class Waiter
98
+ # Tries for +time+ seconds to get the desired result from the given block. Stops when either:
99
+ # 1. The :condition option (which should be a proc) returns true (that is, not false or nil)
100
+ # 2. The block returns true (that is, anything but false or nil) if no :condition option is given
101
+ # 3. The specified amount of time has passed. By default a WaiterError is raised.
102
+ # If :exception option is given, then if it is nil, no exception is raised; otherwise it should be
103
+ # an exception class or an exception instance which will be raised instead of WaiterError
104
+ #
105
+ # Returns the value of the block, which can be handy for things that return nil on failure and some
106
+ # other object on success, like Enumerable#detect for example:
107
+ # found_thing=Waiter.try_for(30) { all_things().detect {|thing| thing.name=="Bill" } }
108
+ #
109
+ # Examples:
110
+ # Waiter.try_for(30) do
111
+ # Time.now.year == 2015
112
+ # end
113
+ # Raises a WaiterError unless it is called between the last 30 seconds of December 31, 2014 and the end of 2015
114
+ #
115
+ # Waiter.try_for(365.242199*24*60*60, :interval => 0.1, :exception => nil, :condition => proc{ 2+2==5 }) do
116
+ # STDERR.puts "any decisecond now ..."
117
+ # end
118
+ # Complains to STDERR for one year, every tenth of a second, as long as 2+2 does not equal 5. Does not
119
+ # raise an exception if 2+2 does not become equal to 5.
120
+ def self.try_for(time, options={})
121
+ unless time.is_a?(Numeric) && options.is_a?(Hash)
122
+ raise TypeError, "expected arguments are time (a numeric) and, optionally, options (a Hash). received arguments #{time.inspect} (#{time.class}), #{options.inspect} (#{options.class})"
123
+ end
124
+ options=handle_options(options, {:interval => 0.5, :condition => proc{|_ret| _ret}, :exception => WaiterError})
125
+ started=Time.now
126
+ begin
127
+ ret=yield
128
+ break if options[:condition].call(ret)
129
+ sleep options[:interval]
130
+ end while Time.now < started+time && !options[:condition].call(ret)
131
+ if options[:exception] && !options[:condition].call(ret)
132
+ ex=if options[:exception].is_a?(Class)
133
+ options[:exception].new("Waiter waited #{time} seconds and condition was not met")
134
+ else
135
+ options[:exception]
136
+ end
137
+ raise ex
138
+ end
139
+ ret
140
+ end
141
+ end