mrjoy-launchpad 0.4.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.
@@ -0,0 +1,456 @@
1
+ require 'helper'
2
+ require 'timeout'
3
+
4
+ class BreakError < StandardError; end
5
+
6
+ describe Launchpad::Interaction do
7
+
8
+ # returns true/false whether the operation ended or the timeout was hit
9
+ def timeout(timeout = 0.02, &block)
10
+ Timeout.timeout(timeout, &block)
11
+ true
12
+ rescue Timeout::Error
13
+ false
14
+ end
15
+
16
+ def press(interaction, type, opts = nil)
17
+ interaction.respond_to(type, :down, opts)
18
+ interaction.respond_to(type, :up, opts)
19
+ end
20
+
21
+
22
+ def press_all(interaction)
23
+ %w(up down left right session user1 user2 mixer).each do |type|
24
+ press(interaction, type.to_sym)
25
+ end
26
+ 8.times do |y|
27
+ 8.times do |x|
28
+ press(interaction, :grid, :x => x, :y => y)
29
+ end
30
+ press(interaction, :"scene#{y + 1}")
31
+ end
32
+ end
33
+
34
+ describe '#initialize' do
35
+
36
+ it 'creates device if not given' do
37
+ device = Launchpad::Device.new
38
+ Launchpad::Device.expects(:new).
39
+ with(:input => true, :output => true, :logger => nil).
40
+ returns(device)
41
+ interaction = Launchpad::Interaction.new
42
+ assert_same device, interaction.device
43
+ end
44
+
45
+ it 'creates device with given device_name' do
46
+ device = Launchpad::Device.new
47
+ Launchpad::Device.expects(:new).
48
+ with(:device_name => 'device', :input => true, :output => true, :logger => nil).
49
+ returns(device)
50
+ interaction = Launchpad::Interaction.new(:device_name => 'device')
51
+ assert_same device, interaction.device
52
+ end
53
+
54
+ it 'creates device with given input_device_id' do
55
+ device = Launchpad::Device.new
56
+ Launchpad::Device.expects(:new).
57
+ with(:input_device_id => 'in', :input => true, :output => true, :logger => nil).
58
+ returns(device)
59
+ interaction = Launchpad::Interaction.new(:input_device_id => 'in')
60
+ assert_same device, interaction.device
61
+ end
62
+
63
+ it 'creates device with given output_device_id' do
64
+ device = Launchpad::Device.new
65
+ Launchpad::Device.expects(:new).
66
+ with(:output_device_id => 'out', :input => true, :output => true, :logger => nil).
67
+ returns(device)
68
+ interaction = Launchpad::Interaction.new(:output_device_id => 'out')
69
+ assert_same device, interaction.device
70
+ end
71
+
72
+ it 'creates device with given input_device_id/output_device_id' do
73
+ device = Launchpad::Device.new
74
+ Launchpad::Device.expects(:new).
75
+ with(:input_device_id => 'in', :output_device_id => 'out', :input => true, :output => true, :logger => nil).
76
+ returns(device)
77
+ interaction = Launchpad::Interaction.new(:input_device_id => 'in', :output_device_id => 'out')
78
+ assert_same device, interaction.device
79
+ end
80
+
81
+ it 'initializes device if given' do
82
+ device = Launchpad::Device.new
83
+ interaction = Launchpad::Interaction.new(:device => device)
84
+ assert_same device, interaction.device
85
+ end
86
+
87
+ it 'stores the logger given' do
88
+ logger = Logger.new(nil)
89
+ interaction = Launchpad::Interaction.new(:logger => logger)
90
+ assert_same logger, interaction.logger
91
+ assert_same logger, interaction.device.logger
92
+ end
93
+
94
+ it 'doesn\'t activate the interaction' do
95
+ assert !Launchpad::Interaction.new.active
96
+ end
97
+
98
+ end
99
+
100
+ describe '#logger=' do
101
+
102
+ it 'stores the logger and passes it to the device as well' do
103
+ logger = Logger.new(nil)
104
+ interaction = Launchpad::Interaction.new
105
+ interaction.logger = logger
106
+ assert_same logger, interaction.logger
107
+ assert_same logger, interaction.device.logger
108
+ end
109
+
110
+ end
111
+
112
+ describe '#close' do
113
+
114
+ it 'stops the interaction' do
115
+ interaction = Launchpad::Interaction.new
116
+ interaction.expects(:stop)
117
+ interaction.close
118
+ end
119
+
120
+ it 'closes the device' do
121
+ interaction = Launchpad::Interaction.new
122
+ interaction.device.expects(:close)
123
+ interaction.close
124
+ end
125
+
126
+ end
127
+
128
+ describe '#closed?' do
129
+
130
+ it 'returns false on a newly created interaction, but true after closing' do
131
+ interaction = Launchpad::Interaction.new
132
+ assert !interaction.closed?
133
+ interaction.close
134
+ assert interaction.closed?
135
+ end
136
+
137
+ end
138
+
139
+ describe '#start' do
140
+
141
+ before do
142
+ @interaction = Launchpad::Interaction.new
143
+ end
144
+
145
+ after do
146
+ mocha_teardown # so that expectations on Thread.join don't fail in here
147
+ begin
148
+ @interaction.close
149
+ rescue
150
+ # ignore, should be handled in tests, this is just to close all the spawned threads
151
+ end
152
+ end
153
+
154
+ it 'sets active to true in blocking mode' do
155
+ refute @interaction.active
156
+ erg = timeout { @interaction.start }
157
+ refute erg, 'there was no timeout'
158
+ assert @interaction.active
159
+ end
160
+
161
+ it 'sets active to true in detached mode' do
162
+ refute @interaction.active
163
+ @interaction.start(:detached => true)
164
+ assert @interaction.active
165
+ end
166
+
167
+ it 'blocks in blocking mode' do
168
+ erg = timeout { @interaction.start }
169
+ refute erg, 'there was no timeout'
170
+ end
171
+
172
+ it 'returns immediately in detached mode' do
173
+ erg = timeout { @interaction.start(:detached => true) }
174
+ assert erg, 'there was a timeout'
175
+ end
176
+
177
+ it 'raises CommunicationError when Portmidi::DeviceError occurs' do
178
+ @interaction.device.stubs(:read_pending_actions).raises(Portmidi::DeviceError.new(0))
179
+ assert_raises Launchpad::CommunicationError do
180
+ @interaction.start
181
+ end
182
+ end
183
+
184
+ describe 'action handling' do
185
+
186
+ before do
187
+ @interaction.response_to(:mixer, :down) { @mixer_down = true }
188
+ @interaction.response_to(:mixer, :up) do |i,a|
189
+ sleep 0.001 # sleep to make "sure" :mixer :down has been processed
190
+ i.stop
191
+ end
192
+ @interaction.device.expects(:read_pending_actions).
193
+ at_least_once.
194
+ returns([
195
+ {
196
+ :timestamp => 0,
197
+ :state => :down,
198
+ :type => :mixer
199
+ },
200
+ {
201
+ :timestamp => 0,
202
+ :state => :up,
203
+ :type => :mixer
204
+ }
205
+ ])
206
+ end
207
+
208
+ it 'calls respond_to_action with actions from respond_to_action in blocking mode' do
209
+ erg = timeout(0.5) { @interaction.start }
210
+ assert erg, 'the actions weren\'t called'
211
+ assert @mixer_down, 'the mixer button wasn\'t pressed'
212
+ end
213
+
214
+ it 'calls respond_to_action with actions from respond_to_action in detached mode' do
215
+ @interaction.start(:detached => true)
216
+ erg = timeout(0.5) { while @interaction.active; sleep 0.01; end }
217
+ assert erg, 'there was a timeout'
218
+ assert @mixer_down, 'the mixer button wasn\'t pressed'
219
+ end
220
+
221
+ end
222
+
223
+ describe 'latency' do
224
+
225
+ before do
226
+ @device = @interaction.device
227
+ @times = []
228
+ @device.instance_variable_set("@test_interaction_latency_times", @times)
229
+ def @device.read_pending_actions
230
+ @test_interaction_latency_times << Time.now.to_f
231
+ []
232
+ end
233
+ end
234
+
235
+ it 'sleeps with default latency of 0.001s when none given' do
236
+ timeout { @interaction.start }
237
+ assert @times.size > 1
238
+ @times.each_cons(2) do |a,b|
239
+ assert_in_delta 0.001, b - a, 0.01
240
+ end
241
+ end
242
+
243
+ it 'sleeps with given latency' do
244
+ @interaction = Launchpad::Interaction.new(:latency => 0.5, :device => @device)
245
+ timeout(0.55) { @interaction.start }
246
+ assert @times.size > 1
247
+ @times.each_cons(2) do |a,b|
248
+ assert_in_delta 0.5, b - a, 0.01
249
+ end
250
+ end
251
+
252
+ it 'sleeps with absolute value of given negative latency' do
253
+ @interaction = Launchpad::Interaction.new(:latency => -0.1, :device => @device)
254
+ timeout(0.15) { @interaction.start }
255
+ assert @times.size > 1
256
+ @times.each_cons(2) do |a,b|
257
+ assert_in_delta 0.1, b - a, 0.01
258
+ end
259
+ end
260
+
261
+ it 'does not sleep when latency is 0' do
262
+ @interaction = Launchpad::Interaction.new(:latency => 0, :device => @device)
263
+ timeout(0.001) { @interaction.start }
264
+ assert @times.size > 1
265
+ @times.each_cons(2) do |a,b|
266
+ assert_in_delta 0, b - a, 0.1
267
+ end
268
+ end
269
+
270
+ end
271
+
272
+ it 'resets the device after the loop' do
273
+ @interaction.device.expects(:reset)
274
+ @interaction.start(:detached => true)
275
+ @interaction.stop
276
+ end
277
+
278
+ it 'raises NoOutputAllowedError on closed interaction' do
279
+ @interaction.close
280
+ assert_raises Launchpad::NoOutputAllowedError do
281
+ @interaction.start
282
+ end
283
+ end
284
+
285
+ end
286
+
287
+ describe '#stop' do
288
+
289
+ before do
290
+ @interaction = Launchpad::Interaction.new
291
+ end
292
+
293
+ it 'sets active to false in blocking mode' do
294
+ erg = timeout { @interaction.start }
295
+ refute erg, 'there was no timeout'
296
+ assert @interaction.active
297
+ @interaction.stop
298
+ assert !@interaction.active
299
+ end
300
+
301
+ it 'sets active to false in detached mode' do
302
+ @interaction.start(:detached => true)
303
+ assert @interaction.active
304
+ @interaction.stop
305
+ assert !@interaction.active
306
+ end
307
+
308
+ it 'is callable anytime' do
309
+ @interaction.stop
310
+ @interaction.start(:detached => true)
311
+ @interaction.stop
312
+ @interaction.stop
313
+ end
314
+
315
+ # this is kinda greybox tested, since I couldn't come up with another way to test tread handling [thomas, 2010-01-24]
316
+ it 'raises pending exceptions in detached mode' do
317
+ t = Thread.new {raise BreakError}
318
+ Thread.expects(:new).returns(t)
319
+ @interaction.start(:detached => true)
320
+ assert_raises BreakError do
321
+ @interaction.stop
322
+ end
323
+ end
324
+
325
+ end
326
+
327
+ describe '#response_to/#no_response_to/#respond_to' do
328
+
329
+ before do
330
+ @interaction = Launchpad::Interaction.new
331
+ end
332
+
333
+ it 'calls all responses that match, and not others' do
334
+ @interaction.response_to(:mixer, :down) {|i, a| @mixer_down = true}
335
+ @interaction.response_to(:all, :down) {|i, a| @all_down = true}
336
+ @interaction.response_to(:all, :up) {|i, a| @all_up = true}
337
+ @interaction.response_to(:grid, :down) {|i, a| @grid_down = true}
338
+ @interaction.respond_to(:mixer, :down)
339
+ assert @mixer_down
340
+ assert @all_down
341
+ assert !@all_up
342
+ assert !@grid_down
343
+ end
344
+
345
+ it 'does not call responses when they are deregistered' do
346
+ @interaction.response_to(:mixer, :down) {|i, a| @mixer_down = true}
347
+ @interaction.response_to(:mixer, :up) {|i, a| @mixer_up = true}
348
+ @interaction.response_to(:all, :both) {|i, a| @all_down = a[:state] == :down}
349
+ @interaction.no_response_to(:mixer, :down)
350
+ @interaction.respond_to(:mixer, :down)
351
+ assert !@mixer_down
352
+ assert !@mixer_up
353
+ assert @all_down
354
+ @interaction.respond_to(:mixer, :up)
355
+ assert !@mixer_down
356
+ assert @mixer_up
357
+ assert !@all_down
358
+ end
359
+
360
+ it 'does not call responses registered for both when removing for one of both states' do
361
+ @interaction.response_to(:mixer, :both) {|i, a| @mixer = true}
362
+ @interaction.no_response_to(:mixer, :down)
363
+ @interaction.respond_to(:mixer, :down)
364
+ assert !@mixer
365
+ @interaction.respond_to(:mixer, :up)
366
+ assert @mixer
367
+ end
368
+
369
+ it 'removes other responses when adding a new exclusive response' do
370
+ @interaction.response_to(:mixer, :both) {|i, a| @mixer = true}
371
+ @interaction.response_to(:mixer, :down, :exclusive => true) {|i, a| @exclusive_mixer = true}
372
+ @interaction.respond_to(:mixer, :down)
373
+ assert !@mixer
374
+ assert @exclusive_mixer
375
+ @interaction.respond_to(:mixer, :up)
376
+ assert @mixer
377
+ assert @exclusive_mixer
378
+ end
379
+
380
+ it 'allows for multiple types' do
381
+ @downs = []
382
+ @interaction.response_to([:up, :down], :down) {|i, a| @downs << a[:type]}
383
+ @interaction.respond_to(:up, :down)
384
+ @interaction.respond_to(:down, :down)
385
+ @interaction.respond_to(:up, :down)
386
+ assert_equal [:up, :down, :up], @downs
387
+ end
388
+
389
+ describe 'allows to bind to specific grid buttons' do
390
+
391
+ before do
392
+ @downs = []
393
+ @action = lambda {|i, a| @downs << [a[:x], a[:y]]}
394
+ end
395
+
396
+ it 'one specific grid button' do
397
+ @interaction.response_to(:grid, :down, :x => 4, :y => 2, &@action)
398
+ press_all @interaction
399
+ assert_equal [[4, 2]], @downs
400
+ end
401
+
402
+ it 'a complete row of grid buttons' do
403
+ @interaction.response_to(:grid, :down, :y => 2, &@action)
404
+ press_all @interaction
405
+ assert_equal [[0, 2], [1, 2], [2, 2], [3, 2], [4, 2], [5, 2], [6, 2], [7, 2]], @downs
406
+ end
407
+
408
+ it 'a complete column of grid buttons' do
409
+ @interaction.response_to(:grid, :down, :x => 3, &@action)
410
+ press_all @interaction
411
+ assert_equal [[3, 0], [3, 1], [3, 2], [3, 3], [3, 4], [3, 5], [3, 6], [3, 7]], @downs
412
+ end
413
+
414
+ it 'a complex range of grid buttons' do
415
+ @interaction.response_to(:grid, :down, :x => [1,[2]], :y => [1, 3..5], &@action)
416
+ press_all @interaction
417
+ assert_equal [[1, 1], [2, 1], [1, 3], [2, 3], [1, 4], [2, 4], [1, 5], [2, 5]], @downs
418
+ end
419
+
420
+ it 'a specific grid buttons, a column, a row, all grid buttons and all buttons' do
421
+ @interaction.response_to(:all, :down) {|i, a| @downs << [a[:x], a[:y], :all]}
422
+ @interaction.response_to(:grid, :down) {|i, a| @downs << [a[:x], a[:y], :grid]}
423
+ @interaction.response_to(:grid, :down, :x => 0) {|i, a| @downs << [a[:x], a[:y], :col]}
424
+ @interaction.response_to(:grid, :down, :y => 0) {|i, a| @downs << [a[:x], a[:y], :row]}
425
+ @interaction.response_to(:grid, :down, :x => 0, :y => 0, &@action)
426
+ press @interaction, :grid, :x => 0, :y => 0
427
+ assert_equal [[0, 0], [0, 0, :col], [0, 0, :row], [0, 0, :grid], [0, 0, :all]], @downs
428
+ end
429
+
430
+ end
431
+
432
+ end
433
+
434
+ describe 'regression tests' do
435
+
436
+ it 'does not raise an exception or write an error to the logger when calling stop within a response in attached mode' do
437
+ log = StringIO.new
438
+ logger = Logger.new(log)
439
+ logger.level = Logger::ERROR
440
+ i = Launchpad::Interaction.new(:logger => logger)
441
+ i.response_to(:mixer, :down) {|i,a| i.stop}
442
+ i.device.expects(:read_pending_actions).
443
+ at_least_once.
444
+ returns([{
445
+ :timestamp => 0,
446
+ :state => :down,
447
+ :type => :mixer
448
+ }])
449
+ erg = timeout { i.start }
450
+ # assert erg, 'the actions weren\'t called'
451
+ assert_equal '', log.string
452
+ end
453
+
454
+ end
455
+
456
+ end