elevate 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|