elevate 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +70 -116
- data/elevate.gemspec +2 -1
- data/lib/elevate/channel.rb +19 -0
- data/lib/elevate/elevate.rb +39 -30
- data/lib/elevate/{promise.rb → future.rb} +7 -7
- data/lib/elevate/http/request.rb +26 -8
- data/lib/elevate/http/response.rb +1 -2
- data/lib/elevate/operation.rb +11 -144
- data/lib/elevate/task.rb +122 -0
- data/lib/elevate/task_context.rb +16 -7
- data/lib/elevate/task_definition.rb +62 -0
- data/lib/elevate/version.rb +1 -1
- data/spec/elevate_spec.rb +258 -106
- data/spec/operation_spec.rb +10 -47
- data/spec/task_context_spec.rb +12 -0
- metadata +21 -19
- data/lib/elevate/callback.rb +0 -22
- data/lib/elevate/dsl.rb +0 -49
- data/spec/callback_spec.rb +0 -22
@@ -9,7 +9,7 @@ module HTTP
|
|
9
9
|
@headers = nil
|
10
10
|
@status_code = nil
|
11
11
|
@error = nil
|
12
|
-
@raw_body =
|
12
|
+
@raw_body = NSMutableData.alloc.init
|
13
13
|
@url = nil
|
14
14
|
end
|
15
15
|
|
@@ -17,7 +17,6 @@ module HTTP
|
|
17
17
|
#
|
18
18
|
# @api private
|
19
19
|
def append_data(data)
|
20
|
-
@raw_body ||= NSMutableData.alloc.init
|
21
20
|
@raw_body.appendData(data)
|
22
21
|
end
|
23
22
|
|
data/lib/elevate/operation.rb
CHANGED
@@ -8,33 +8,12 @@ module Elevate
|
|
8
8
|
# newly initialized instance
|
9
9
|
#
|
10
10
|
# @api private
|
11
|
-
def initWithTarget(target, args:args)
|
11
|
+
def initWithTarget(target, args: args, channel: channel)
|
12
12
|
if init
|
13
13
|
@coordinator = IOCoordinator.new
|
14
|
-
@context = TaskContext.new(
|
15
|
-
@
|
16
|
-
@
|
17
|
-
@update_callback = nil
|
18
|
-
@finish_callback = nil
|
19
|
-
|
20
|
-
setCompletionBlock(lambda do
|
21
|
-
if @finish_callback
|
22
|
-
@finish_callback.call(@result, @exception) unless isCancelled
|
23
|
-
end
|
24
|
-
|
25
|
-
Dispatch::Queue.main.sync do
|
26
|
-
@context = nil
|
27
|
-
|
28
|
-
if @timer
|
29
|
-
@timer.invalidate
|
30
|
-
@timer = nil
|
31
|
-
end
|
32
|
-
|
33
|
-
@timeout_callback = nil
|
34
|
-
@update_callback = nil
|
35
|
-
@finish_callback = nil
|
36
|
-
end
|
37
|
-
end)
|
14
|
+
@context = TaskContext.new(target, channel, args)
|
15
|
+
@exception = nil
|
16
|
+
@result = nil
|
38
17
|
end
|
39
18
|
|
40
19
|
self
|
@@ -51,56 +30,26 @@ module Elevate
|
|
51
30
|
super
|
52
31
|
end
|
53
32
|
|
54
|
-
# Returns information about this task.
|
55
|
-
#
|
56
|
-
# @return [String]
|
57
|
-
# String suitable for debugging purposes.
|
58
|
-
#
|
59
|
-
# @api public
|
60
|
-
def inspect
|
61
|
-
details = []
|
62
|
-
details << "<canceled>" if @coordinator.cancelled?
|
63
|
-
|
64
|
-
"#<#{self.class.name}: #{details.join(" ")}>"
|
65
|
-
end
|
66
|
-
|
67
|
-
# Logs debugging information in certain configurations.
|
68
|
-
#
|
69
|
-
# @return [void]
|
70
|
-
#
|
71
|
-
# @api private
|
72
|
-
def log(line)
|
73
|
-
puts line unless RUBYMOTION_ENV == "test"
|
74
|
-
end
|
75
|
-
|
76
33
|
# Runs the specified task.
|
77
34
|
#
|
78
35
|
# @return [void]
|
79
36
|
#
|
80
37
|
# @api private
|
81
38
|
def main
|
82
|
-
log " START: #{inspect}"
|
83
|
-
|
84
39
|
@coordinator.install
|
85
40
|
|
86
41
|
begin
|
87
42
|
unless @coordinator.cancelled?
|
88
|
-
@result = @context.execute
|
89
|
-
@update_callback.call(*args) if @update_callback
|
90
|
-
end
|
43
|
+
@result = @context.execute
|
91
44
|
end
|
92
45
|
|
93
|
-
rescue
|
46
|
+
rescue => e
|
94
47
|
@exception = e
|
95
|
-
|
96
|
-
if e.is_a?(TimeoutError)
|
97
|
-
@timeout_callback.call if @timeout_callback
|
98
|
-
end
|
99
48
|
end
|
100
49
|
|
101
50
|
@coordinator.uninstall
|
102
51
|
|
103
|
-
|
52
|
+
@context = nil
|
104
53
|
end
|
105
54
|
|
106
55
|
# Returns the exception that terminated this task, if any.
|
@@ -123,96 +72,14 @@ module Elevate
|
|
123
72
|
# @api public
|
124
73
|
attr_reader :result
|
125
74
|
|
126
|
-
#
|
127
|
-
#
|
128
|
-
#
|
129
|
-
# @param callback [Elevate::Callback]
|
130
|
-
# completion callback
|
131
|
-
#
|
132
|
-
# @return [void]
|
133
|
-
#
|
134
|
-
# @api private
|
135
|
-
def on_finish=(callback)
|
136
|
-
@finish_callback = callback
|
137
|
-
end
|
138
|
-
|
139
|
-
# Sets the callback to be run when this task is queued.
|
140
|
-
#
|
141
|
-
# Do not call this method after the task has started.
|
142
|
-
#
|
143
|
-
# @param callback [Elevate::Callback]
|
144
|
-
# callback to be invoked when queueing
|
145
|
-
#
|
146
|
-
# @return [void]
|
147
|
-
#
|
148
|
-
# @api private
|
149
|
-
def on_start=(callback)
|
150
|
-
start_callback = callback
|
151
|
-
start_callback.retain
|
152
|
-
|
153
|
-
Dispatch::Queue.main.async do
|
154
|
-
start_callback.call unless isCancelled
|
155
|
-
start_callback.release
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
# Handles timeout expiration.
|
160
|
-
#
|
161
|
-
# @return [void]
|
162
|
-
#
|
163
|
-
# @api private
|
164
|
-
def on_timeout_elapsed(timer)
|
165
|
-
@coordinator.cancel(TimeoutError)
|
166
|
-
end
|
167
|
-
|
168
|
-
# Sets the timeout callback.
|
169
|
-
#
|
170
|
-
# @param callback [Elevate::Callback]
|
171
|
-
# callback to run on timeout
|
172
|
-
#
|
173
|
-
# @return [void]
|
174
|
-
#
|
175
|
-
# @api private
|
176
|
-
def on_timeout=(callback)
|
177
|
-
@timeout_callback = callback
|
178
|
-
end
|
179
|
-
|
180
|
-
# Sets the update callback, which is invoked for any yield statements in the task.
|
181
|
-
#
|
182
|
-
# @param callback [Elevate::Callback]
|
183
|
-
# @return [void]
|
184
|
-
#
|
185
|
-
# @api private
|
186
|
-
def on_update=(callback)
|
187
|
-
@update_callback = callback
|
188
|
-
end
|
189
|
-
|
190
|
-
# Sets the timeout interval for this task.
|
191
|
-
#
|
192
|
-
# The timeout starts when the task is queued, not when it is started.
|
193
|
-
#
|
194
|
-
# @param interval [Fixnum]
|
195
|
-
# seconds to allow for task completion
|
75
|
+
# Cancels any waiting operation with a TimeoutError, interrupting
|
76
|
+
# execution. This is not the same as #cancel.
|
196
77
|
#
|
197
78
|
# @return [void]
|
198
79
|
#
|
199
|
-
# @api private
|
200
|
-
def timeout=(interval)
|
201
|
-
@timer = NSTimer.scheduledTimerWithTimeInterval(interval,
|
202
|
-
target: self,
|
203
|
-
selector: :"on_timeout_elapsed:",
|
204
|
-
userInfo: nil,
|
205
|
-
repeats: false)
|
206
|
-
end
|
207
|
-
|
208
|
-
# Returns whether this task timed out.
|
209
|
-
#
|
210
|
-
# @return [Boolean]
|
211
|
-
# true if this task was aborted due to a time out.
|
212
|
-
#
|
213
80
|
# @api public
|
214
|
-
def
|
215
|
-
@
|
81
|
+
def timeout
|
82
|
+
@coordinator.cancel(TimeoutError)
|
216
83
|
end
|
217
84
|
end
|
218
85
|
end
|
data/lib/elevate/task.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
module Elevate
|
2
|
+
class Task
|
3
|
+
def initialize(definition, controller, active_tasks)
|
4
|
+
@definition = definition
|
5
|
+
@controller = WeakRef.new(controller)
|
6
|
+
@active_tasks = active_tasks
|
7
|
+
@operation = nil
|
8
|
+
@channel = Channel.new(method(:on_update))
|
9
|
+
@args = nil
|
10
|
+
@timer = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def cancel
|
14
|
+
if @operation
|
15
|
+
@operation.cancel
|
16
|
+
|
17
|
+
if @timer
|
18
|
+
@timer.invalidate
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def name
|
24
|
+
@definition.name
|
25
|
+
end
|
26
|
+
|
27
|
+
def start(args)
|
28
|
+
@operation = ElevateOperation.alloc.initWithTarget(@definition.handlers[:background],
|
29
|
+
args: args,
|
30
|
+
channel: WeakRef.new(@channel))
|
31
|
+
|
32
|
+
@operation.addObserver(self, forKeyPath: "isFinished", options: NSKeyValueObservingOptionNew, context: nil)
|
33
|
+
queue.addOperation(@operation)
|
34
|
+
@active_tasks << self
|
35
|
+
|
36
|
+
@args = args
|
37
|
+
|
38
|
+
if interval = @definition.options[:timeout_interval]
|
39
|
+
@timer = NSTimer.scheduledTimerWithTimeInterval(interval,
|
40
|
+
target: self,
|
41
|
+
selector: :"on_timeout:",
|
42
|
+
userInfo: nil,
|
43
|
+
repeats: false)
|
44
|
+
end
|
45
|
+
|
46
|
+
performSelectorOnMainThread(:on_start, withObject: nil, waitUntilDone: false)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def error_handler_for(exception)
|
52
|
+
handler_name = exception.class.name
|
53
|
+
handler_name = handler_name.split("::").last
|
54
|
+
handler_name.gsub!(/Error$/, "")
|
55
|
+
handler_name.gsub!(/(.)([A-Z])/) { |m| "#{$1}_#{$2.downcase}" }
|
56
|
+
handler_name = "on_" + handler_name.downcase
|
57
|
+
|
58
|
+
handler_name.to_sym
|
59
|
+
end
|
60
|
+
|
61
|
+
def invoke(handler_name, *args)
|
62
|
+
return false if @operation.isCancelled
|
63
|
+
|
64
|
+
block = @definition.handlers[handler_name]
|
65
|
+
return false unless block
|
66
|
+
|
67
|
+
@controller.task_args = @args
|
68
|
+
@controller.instance_exec(*args, &block)
|
69
|
+
@controller.task_args = nil
|
70
|
+
|
71
|
+
true
|
72
|
+
end
|
73
|
+
|
74
|
+
def queue
|
75
|
+
Dispatch.once do
|
76
|
+
$elevate_queue = NSOperationQueue.alloc.init
|
77
|
+
$elevate_queue.maxConcurrentOperationCount = 1
|
78
|
+
end
|
79
|
+
|
80
|
+
$elevate_queue
|
81
|
+
end
|
82
|
+
|
83
|
+
def observeValueForKeyPath(path, ofObject: operation, change: change, context: ctx)
|
84
|
+
case path
|
85
|
+
when "isFinished"
|
86
|
+
performSelectorOnMainThread(:on_finish, withObject: nil, waitUntilDone: false)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def on_start
|
91
|
+
invoke(:on_start)
|
92
|
+
end
|
93
|
+
|
94
|
+
def on_finish
|
95
|
+
@operation.removeObserver(self, forKeyPath: "isFinished")
|
96
|
+
@active_tasks.delete(self)
|
97
|
+
|
98
|
+
if @timer
|
99
|
+
@timer.invalidate
|
100
|
+
end
|
101
|
+
|
102
|
+
if exception = @operation.exception
|
103
|
+
invoke(error_handler_for(exception), exception) || invoke(:on_error, exception)
|
104
|
+
end
|
105
|
+
|
106
|
+
invoke(:on_finish, @operation.result, @operation.exception)
|
107
|
+
end
|
108
|
+
|
109
|
+
def on_timeout(timer)
|
110
|
+
@operation.timeout
|
111
|
+
end
|
112
|
+
|
113
|
+
def on_update(args)
|
114
|
+
unless NSThread.isMainThread
|
115
|
+
performSelectorOnMainThread(:"on_update:", withObject: args, waitUntilDone: false)
|
116
|
+
return
|
117
|
+
end
|
118
|
+
|
119
|
+
invoke(:on_update, *args)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
data/lib/elevate/task_context.rb
CHANGED
@@ -2,17 +2,26 @@ module Elevate
|
|
2
2
|
# A blank slate for hosting task blocks.
|
3
3
|
#
|
4
4
|
# Because task blocks run in another thread, it is dangerous to expose them
|
5
|
-
# to the calling context. This class acts as a sandbox for task blocks.
|
5
|
+
# to the calling context. This class acts as a sandbox for task blocks.
|
6
6
|
#
|
7
7
|
# @api private
|
8
8
|
class TaskContext
|
9
|
-
def initialize(
|
10
|
-
|
11
|
-
|
9
|
+
def initialize(block, channel, args)
|
10
|
+
@__block = block
|
11
|
+
@__channel = channel
|
12
|
+
@__args = args
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute
|
16
|
+
instance_exec(&@__block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def task_args
|
20
|
+
@__args
|
21
|
+
end
|
12
22
|
|
13
|
-
|
14
|
-
|
15
|
-
end
|
23
|
+
def update(*args)
|
24
|
+
@__channel << args if @__channel
|
16
25
|
end
|
17
26
|
end
|
18
27
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Elevate
|
2
|
+
class TaskDefinition
|
3
|
+
def initialize(name, options, &block)
|
4
|
+
@name = name
|
5
|
+
@options = options
|
6
|
+
@handlers = {}
|
7
|
+
|
8
|
+
instance_eval(&block)
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :name
|
12
|
+
attr_reader :handlers
|
13
|
+
attr_reader :options
|
14
|
+
|
15
|
+
def method_missing(method, *args, &block)
|
16
|
+
if method.to_s.start_with?("on_")
|
17
|
+
raise ArgumentError, "wrong number of arguments" unless args.empty?
|
18
|
+
raise ArgumentError, "block not supplied" unless block_given?
|
19
|
+
|
20
|
+
@handlers[method.to_sym] = block
|
21
|
+
else
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def respond_to_missing?(method, include_private = false)
|
27
|
+
method.to_s.start_with?("on_") || super
|
28
|
+
end
|
29
|
+
|
30
|
+
def background(&block)
|
31
|
+
@handlers[:background] = block
|
32
|
+
end
|
33
|
+
|
34
|
+
def on_error(&block)
|
35
|
+
raise "on_error blocks must accept one parameter" unless block.arity == 1
|
36
|
+
|
37
|
+
@handlers[:on_error] = block
|
38
|
+
end
|
39
|
+
|
40
|
+
def on_finish(&block)
|
41
|
+
raise "on_finish blocks must accept two parameters" unless block.arity == 2
|
42
|
+
|
43
|
+
@handlers[:on_finish] = block
|
44
|
+
end
|
45
|
+
|
46
|
+
def on_start(&block)
|
47
|
+
raise "on_start blocks must accept zero parameters" unless block.arity == 0
|
48
|
+
|
49
|
+
@handlers[:on_start] = block
|
50
|
+
end
|
51
|
+
|
52
|
+
def on_update(&block)
|
53
|
+
@handlers[:on_update] = block
|
54
|
+
end
|
55
|
+
|
56
|
+
def timeout(seconds)
|
57
|
+
raise "timeout argument must be a number" unless seconds.is_a?(Numeric)
|
58
|
+
|
59
|
+
@options[:timeout_interval] = seconds
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/elevate/version.rb
CHANGED
data/spec/elevate_spec.rb
CHANGED
@@ -1,159 +1,311 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
class TestController
|
2
|
+
include Elevate
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@invocations = {}
|
6
|
+
@counter = 0
|
7
|
+
@threads = []
|
8
|
+
@updates = []
|
9
|
+
@callback_args = nil
|
10
|
+
@completion = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :started
|
14
|
+
attr_accessor :result
|
15
|
+
attr_accessor :exception
|
16
|
+
attr_accessor :invocations
|
17
|
+
attr_accessor :counter
|
18
|
+
attr_accessor :threads
|
19
|
+
attr_accessor :updates
|
20
|
+
attr_accessor :callback_args
|
21
|
+
attr_accessor :completion
|
22
|
+
|
23
|
+
task :cancellable do
|
24
|
+
background do
|
25
|
+
task_args[:semaphore].wait
|
26
|
+
update 42
|
27
|
+
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
on_start do
|
32
|
+
self.invocations[:start] = counter
|
33
|
+
self.counter += 1
|
34
|
+
end
|
35
|
+
|
36
|
+
on_finish do |result, ex|
|
37
|
+
self.invocations[:finish] = counter
|
38
|
+
self.counter += 1
|
39
|
+
end
|
40
|
+
|
41
|
+
on_update do |n|
|
42
|
+
self.invocations[:update] = counter
|
43
|
+
self.counter += 1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
task :custom_error_handlers do
|
48
|
+
background do
|
49
|
+
raise TimeoutError
|
50
|
+
end
|
51
|
+
|
52
|
+
on_error do |e|
|
53
|
+
self.invocations[:error] = counter
|
54
|
+
self.counter += 1
|
55
|
+
end
|
56
|
+
|
57
|
+
on_timeout do |e|
|
58
|
+
self.invocations[:timeout] = counter
|
59
|
+
self.counter += 1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
task :test_task do
|
64
|
+
background do
|
65
|
+
sleep 0.05
|
66
|
+
update 1
|
67
|
+
raise Elevate::TimeoutError if task_args[:raise]
|
68
|
+
sleep 0.1
|
69
|
+
update 2
|
70
|
+
|
71
|
+
42
|
72
|
+
end
|
73
|
+
|
74
|
+
on_start do
|
75
|
+
self.invocations[:start] = counter
|
76
|
+
self.callback_args = task_args
|
77
|
+
self.counter += 1
|
78
|
+
|
79
|
+
self.threads << NSThread.currentThread
|
80
|
+
end
|
81
|
+
|
82
|
+
on_update do |num|
|
83
|
+
self.updates << num
|
84
|
+
|
85
|
+
self.invocations[:update] = counter
|
86
|
+
self.counter += 1
|
87
|
+
|
88
|
+
self.threads << NSThread.currentThread
|
89
|
+
end
|
90
|
+
|
91
|
+
on_error do |e|
|
92
|
+
self.invocations[:error] = counter
|
93
|
+
self.counter += 1
|
94
|
+
end
|
95
|
+
|
96
|
+
on_finish do |result, exception|
|
97
|
+
self.invocations[:finish] = counter
|
98
|
+
self.counter += 1
|
99
|
+
|
100
|
+
self.threads << NSThread.currentThread
|
101
|
+
|
102
|
+
self.result = result
|
103
|
+
self.exception = exception
|
104
|
+
|
105
|
+
#Dispatch::Queue.main.async { resume }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
task :timeout_test do
|
110
|
+
timeout 0.3
|
111
|
+
|
112
|
+
background do
|
113
|
+
Elevate::HTTP.get("http://example.com/")
|
114
|
+
end
|
115
|
+
|
116
|
+
on_timeout do |e|
|
117
|
+
self.invocations[:timeout] = counter
|
118
|
+
self.counter += 1
|
119
|
+
end
|
120
|
+
|
121
|
+
on_finish do |result, ex|
|
122
|
+
self.invocations[:finish] = counter
|
123
|
+
self.counter += 1
|
124
|
+
|
125
|
+
@completion.call if @completion
|
126
|
+
end
|
4
127
|
end
|
5
128
|
end
|
6
129
|
|
7
130
|
describe Elevate do
|
8
131
|
extend WebStub::SpecHelpers
|
9
132
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
task do
|
14
|
-
true
|
15
|
-
end
|
133
|
+
before do
|
134
|
+
@controller = TestController.new
|
135
|
+
end
|
16
136
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
137
|
+
describe "#cancel" do
|
138
|
+
describe "when no tasks are running" do
|
139
|
+
it "does nothing" do
|
140
|
+
->{ @controller.cancel(:test_task) }.should.not.raise
|
21
141
|
end
|
142
|
+
end
|
22
143
|
|
23
|
-
|
24
|
-
|
144
|
+
describe "when a single task is running" do
|
145
|
+
it "cancels the task and does not invoke callbacks" do
|
146
|
+
semaphore = Dispatch::Semaphore.new(0)
|
147
|
+
@controller.launch(:cancellable, semaphore: semaphore)
|
148
|
+
|
149
|
+
@controller.cancel(:cancellable)
|
150
|
+
semaphore.signal
|
151
|
+
|
152
|
+
wait 0.5 do
|
153
|
+
@controller.invocations[:update].should.be.nil
|
154
|
+
@controller.invocations[:finish].should.be.nil
|
155
|
+
end
|
25
156
|
end
|
26
157
|
end
|
27
158
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
159
|
+
describe "when several tasks are running" do
|
160
|
+
it "cancels all of them" do
|
161
|
+
semaphore = Dispatch::Semaphore.new(0)
|
162
|
+
|
163
|
+
@controller.launch(:cancellable, semaphore: semaphore)
|
164
|
+
@controller.launch(:cancellable, semaphore: semaphore)
|
165
|
+
@controller.launch(:cancellable, semaphore: semaphore)
|
166
|
+
|
167
|
+
@controller.cancel(:cancellable)
|
168
|
+
semaphore.signal
|
33
169
|
|
34
|
-
|
35
|
-
@
|
36
|
-
|
170
|
+
wait 0.5 do
|
171
|
+
@controller.invocations[:update].should.be.nil
|
172
|
+
@controller.invocations[:finish].should.be.nil
|
37
173
|
end
|
174
|
+
|
38
175
|
end
|
176
|
+
end
|
177
|
+
end
|
39
178
|
|
40
|
-
|
41
|
-
|
179
|
+
describe "#launch" do
|
180
|
+
it "runs the task asynchronously, returning the result" do
|
181
|
+
@controller.launch(:test_task, raise: false)
|
182
|
+
|
183
|
+
wait 0.5 do
|
184
|
+
@controller.result.should == 42
|
42
185
|
end
|
43
186
|
end
|
44
187
|
|
45
188
|
it "allows tasks to report progress" do
|
46
|
-
@
|
47
|
-
|
48
|
-
async do
|
49
|
-
task do
|
50
|
-
sleep 0.1
|
51
|
-
yield 1
|
52
|
-
sleep 0.2
|
53
|
-
yield 2
|
54
|
-
sleep 0.3
|
55
|
-
yield 3
|
56
|
-
|
57
|
-
true
|
58
|
-
end
|
189
|
+
@controller.launch(:test_task, raise: false)
|
59
190
|
|
60
|
-
|
61
|
-
|
62
|
-
end
|
63
|
-
|
64
|
-
on_finish do |result, exception|
|
65
|
-
resume
|
66
|
-
end
|
191
|
+
wait 0.5 do
|
192
|
+
@controller.updates.should == [1, 2]
|
67
193
|
end
|
194
|
+
end
|
68
195
|
|
69
|
-
|
70
|
-
|
196
|
+
it "invokes all callbacks on the UI thread" do
|
197
|
+
@controller.launch(:test_task, raise: false)
|
198
|
+
|
199
|
+
wait 0.5 do
|
200
|
+
@controller.threads.each { |t| t.isMainThread.should.be.true }
|
71
201
|
end
|
72
202
|
end
|
73
203
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
204
|
+
it "sets task_args to args used at launch" do
|
205
|
+
@controller.launch(:test_task, raise: true)
|
206
|
+
|
207
|
+
wait 0.5 do
|
208
|
+
@controller.callback_args.should == { raise: true }
|
78
209
|
end
|
210
|
+
end
|
79
211
|
|
80
|
-
|
81
|
-
|
212
|
+
it "invokes on_error when an exception occurs" do
|
213
|
+
@controller.launch(:test_task, raise: true)
|
82
214
|
|
83
|
-
|
84
|
-
|
215
|
+
wait 0.5 do
|
216
|
+
@controller.invocations[:error].should.not.be.nil
|
217
|
+
end
|
218
|
+
end
|
85
219
|
|
86
|
-
|
87
|
-
|
220
|
+
it "invokes on_start before on_finish" do
|
221
|
+
@controller.launch(:test_task, raise: false)
|
88
222
|
|
89
|
-
|
90
|
-
|
223
|
+
wait 0.5 do
|
224
|
+
@controller.invocations[:start].should < @controller.invocations[:finish]
|
225
|
+
end
|
226
|
+
end
|
91
227
|
|
92
|
-
|
93
|
-
|
94
|
-
resume
|
95
|
-
end
|
96
|
-
end
|
228
|
+
it "invokes on_update before on_finish" do
|
229
|
+
@controller.launch(:test_task, raise: false)
|
97
230
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
231
|
+
wait 0.5 do
|
232
|
+
invocations = @controller.invocations
|
233
|
+
|
234
|
+
invocations[:update].should < invocations[:finish]
|
102
235
|
end
|
236
|
+
end
|
103
237
|
|
104
|
-
|
105
|
-
|
238
|
+
it "invokes on_update after on_start" do
|
239
|
+
@controller.launch(:test_task, raise: false)
|
240
|
+
|
241
|
+
wait 0.5 do
|
242
|
+
invocations = @controller.invocations
|
243
|
+
|
244
|
+
invocations[:update].should > invocations[:start]
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
106
248
|
|
107
|
-
|
108
|
-
|
249
|
+
describe "error handling" do
|
250
|
+
it "invokes an error handling correspding to the raised exception" do
|
251
|
+
@controller.launch(:custom_error_handlers)
|
109
252
|
|
110
|
-
|
111
|
-
|
253
|
+
wait 0.5 do
|
254
|
+
invocations = @controller.invocations
|
112
255
|
|
113
|
-
|
114
|
-
|
256
|
+
invocations[:timeout].should.not.be.nil
|
257
|
+
invocations[:error].should.be.nil
|
258
|
+
end
|
259
|
+
end
|
115
260
|
|
116
|
-
|
117
|
-
|
118
|
-
resume
|
119
|
-
end
|
120
|
-
end
|
261
|
+
it "invokes on_error if there is not a specific handler" do
|
262
|
+
@controller.launch(:test_task, raise: true)
|
121
263
|
|
122
|
-
|
123
|
-
|
264
|
+
wait 0.5 do
|
265
|
+
invocations = @controller.invocations
|
124
266
|
|
125
|
-
|
126
|
-
end
|
267
|
+
invocations[:error].should.not.be.nil
|
127
268
|
end
|
269
|
+
end
|
270
|
+
end
|
128
271
|
|
129
|
-
|
130
|
-
|
131
|
-
|
272
|
+
describe "timeouts" do
|
273
|
+
it "does not cancel the operation if it completes in time" do
|
274
|
+
stub_request(:get, "http://example.com/").
|
275
|
+
to_return(body: "Hello!", content_type: "text/plain")
|
132
276
|
|
133
|
-
|
134
|
-
|
277
|
+
@controller.completion = -> { resume }
|
278
|
+
@controller.launch(:timeout_test)
|
135
279
|
|
136
|
-
|
137
|
-
|
280
|
+
wait_max 0.9 do
|
281
|
+
@controller.invocations[:timeout].should.be.nil
|
282
|
+
@controller.invocations[:finish].should.not.be.nil
|
283
|
+
end
|
284
|
+
end
|
138
285
|
|
139
|
-
|
140
|
-
|
286
|
+
it "stops the operation when it exceeds the timeout" do
|
287
|
+
stub_request(:get, "http://example.com/").
|
288
|
+
to_return(body: "Hello!", content_type: "text/plain", delay: 1.0)
|
141
289
|
|
142
|
-
|
143
|
-
|
144
|
-
end
|
290
|
+
@controller.completion = -> { resume }
|
291
|
+
@controller.launch(:timeout_test)
|
145
292
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
end
|
293
|
+
wait_max 0.9 do
|
294
|
+
@controller.invocations[:finish].should.not.be.nil
|
295
|
+
end
|
296
|
+
end
|
151
297
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
298
|
+
it "invokes on_timeout when the operation times out" do
|
299
|
+
stub_request(:get, "http://example.com/").
|
300
|
+
to_return(body: "Hello!", content_type: "text/plain", delay: 1.0)
|
301
|
+
|
302
|
+
@controller.completion = -> { resume }
|
303
|
+
@controller.launch(:timeout_test)
|
304
|
+
|
305
|
+
wait_max 0.9 do
|
306
|
+
@controller.invocations[:timeout].should.not.be.nil
|
156
307
|
end
|
308
|
+
|
157
309
|
end
|
158
310
|
end
|
159
311
|
end
|