test/test_interaction.rb in launchpad-0.2.2 vs test/test_interaction.rb in launchpad-0.3.0
- old
+ new
@@ -1,237 +1,338 @@
require 'helper'
+require 'timeout'
class BreakError < StandardError; end
-class TestInteraction < Test::Unit::TestCase
-
- context 'initializer' do
+describe Launchpad::Interaction do
+
+ # returns true/false whether the operation ended or the timeout was hit
+ def timeout(timeout = 0.02, &block)
+ Timeout.timeout(timeout, &block)
+ true
+ rescue Timeout::Error
+ false
+ end
+
+ def press(interaction, type, opts = nil)
+ interaction.respond_to(type, :down, opts)
+ interaction.respond_to(type, :up, opts)
+ end
+
+
+ def press_all(interaction)
+ %w(up down left right session user1 user2 mixer).each do |type|
+ press(interaction, type.to_sym)
+ end
+ 8.times do |y|
+ 8.times do |x|
+ press(interaction, :grid, :x => x, :y => y)
+ end
+ press(interaction, :"scene#{y + 1}")
+ end
+ end
+
+ describe '#initialize' do
- should 'create device if not given' do
- Launchpad::Device.expects(:new).with(:input => true, :output => true).returns('device')
- assert_equal 'device', Launchpad::Interaction.new.device
+ it 'creates device if not given' do
+ device = Launchpad::Device.new
+ Launchpad::Device.expects(:new).
+ with(:input => true, :output => true, :logger => nil).
+ returns(device)
+ interaction = Launchpad::Interaction.new
+ assert_same device, interaction.device
end
- should 'create device with given device_name' do
- Launchpad::Device.expects(:new).with(:device_name => 'device', :input => true, :output => true).returns('device')
- assert_equal 'device', Launchpad::Interaction.new(:device_name => 'device').device
+ it 'creates device with given device_name' do
+ device = Launchpad::Device.new
+ Launchpad::Device.expects(:new).
+ with(:device_name => 'device', :input => true, :output => true, :logger => nil).
+ returns(device)
+ interaction = Launchpad::Interaction.new(:device_name => 'device')
+ assert_same device, interaction.device
end
- should 'create device with given input_device_id/output_device_id' do
- Launchpad::Device.expects(:new).with(:input_device_id => 'in', :output_device_id => 'out', :input => true, :output => true).returns('device')
- assert_equal 'device', Launchpad::Interaction.new(:input_device_id => 'in', :output_device_id => 'out').device
+ it 'creates device with given input_device_id' do
+ device = Launchpad::Device.new
+ Launchpad::Device.expects(:new).
+ with(:input_device_id => 'in', :input => true, :output => true, :logger => nil).
+ returns(device)
+ interaction = Launchpad::Interaction.new(:input_device_id => 'in')
+ assert_same device, interaction.device
end
- should 'initialize device if given' do
- assert_equal 'device', Launchpad::Interaction.new(:device => 'device').device
+ it 'creates device with given output_device_id' do
+ device = Launchpad::Device.new
+ Launchpad::Device.expects(:new).
+ with(:output_device_id => 'out', :input => true, :output => true, :logger => nil).
+ returns(device)
+ interaction = Launchpad::Interaction.new(:output_device_id => 'out')
+ assert_same device, interaction.device
end
- should 'not be active' do
+ it 'creates device with given input_device_id/output_device_id' do
+ device = Launchpad::Device.new
+ Launchpad::Device.expects(:new).
+ with(:input_device_id => 'in', :output_device_id => 'out', :input => true, :output => true, :logger => nil).
+ returns(device)
+ interaction = Launchpad::Interaction.new(:input_device_id => 'in', :output_device_id => 'out')
+ assert_same device, interaction.device
+ end
+
+ it 'initializes device if given' do
+ device = Launchpad::Device.new
+ interaction = Launchpad::Interaction.new(:device => device)
+ assert_same device, interaction.device
+ end
+
+ it 'stores the logger given' do
+ logger = Logger.new(nil)
+ interaction = Launchpad::Interaction.new(:logger => logger)
+ assert_same logger, interaction.logger
+ assert_same logger, interaction.device.logger
+ end
+
+ it 'doesn\'t activate the interaction' do
assert !Launchpad::Interaction.new.active
end
end
+
+ describe '#logger=' do
+
+ it 'stores the logger and passes it to the device as well' do
+ logger = Logger.new(nil)
+ interaction = Launchpad::Interaction.new
+ interaction.logger = logger
+ assert_same logger, interaction.logger
+ assert_same logger, interaction.device.logger
+ end
+
+ end
- context 'close' do
-
- should 'not be active' do
+ describe '#close' do
+
+ it 'stops the interaction' do
interaction = Launchpad::Interaction.new
- interaction.start(:detached => true)
+ interaction.expects(:stop)
interaction.close
- assert !interaction.active
end
- should 'close device' do
- interaction = Launchpad::Interaction.new(:device => device = Launchpad::Device.new)
- device.expects(:close)
+ it 'closes the device' do
+ interaction = Launchpad::Interaction.new
+ interaction.device.expects(:close)
interaction.close
end
end
- context 'closed?' do
+ describe '#closed?' do
- should 'return false on a newly created interaction, but true after closing' do
+ it 'returns false on a newly created interaction, but true after closing' do
interaction = Launchpad::Interaction.new
assert !interaction.closed?
interaction.close
assert interaction.closed?
end
end
- context 'start' do
+ describe '#start' do
- setup do
- @interaction = Launchpad::Interaction.new(:device => @device = Launchpad::Device.new)
+ before do
+ @interaction = Launchpad::Interaction.new
end
- teardown do
+ after do
+ mocha_teardown # so that expectations on Thread.join don't fail in here
begin
@interaction.close
rescue
# ignore, should be handled in tests, this is just to close all the spawned threads
end
end
- # this is kinda greybox tested, since I couldn't come up with another way to test thread handling [thomas, 2010-01-24]
- should 'set active to true in blocking mode' do
- t = Thread.new {}
- Thread.expects(:new).returns(t)
- @interaction.start
+ it 'sets active to true in blocking mode' do
+ refute @interaction.active
+ erg = timeout { @interaction.start }
+ refute erg, 'there was no timeout'
assert @interaction.active
end
- should 'set active to true in detached mode' do
+ it 'sets active to true in detached mode' do
+ refute @interaction.active
@interaction.start(:detached => true)
assert @interaction.active
end
- # this is kinda greybox tested, since I couldn't come up with another way to test thread handling [thomas, 2010-01-24]
- should 'start a new thread and block in blocking mode' do
- t = Thread.new {}
- Thread.expects(:new).returns(t)
- t.expects(:join)
- @interaction.start
+ it 'blocks in blocking mode' do
+ erg = timeout { @interaction.start }
+ refute erg, 'there was no timeout'
end
- # this is kinda greybox tested, since I couldn't come up with another way to test thread handling [thomas, 2010-01-24]
- should 'start a new thread and return in detached mode' do
- t = Thread.new {}
- Thread.expects(:new).returns(t)
- t.expects(:join).never
- @interaction.start(:detached => true)
+ it 'returns immediately in detached mode' do
+ erg = timeout { @interaction.start(:detached => true) }
+ assert erg, 'there was a timeout'
end
- should 'raise CommunicationError when Portmidi::DeviceError occurs' do
- @device.stubs(:read_pending_actions).raises(Portmidi::DeviceError.new(0))
- assert_raise Launchpad::CommunicationError do
+ it 'raises CommunicationError when Portmidi::DeviceError occurs' do
+ @interaction.device.stubs(:read_pending_actions).raises(Portmidi::DeviceError.new(0))
+ assert_raises Launchpad::CommunicationError do
@interaction.start
end
end
-
- # this is kinda greybox tested, since I couldn't come up with another way to test thread handling [thomas, 2010-01-24]
- should 'call respond_to_action with actions from respond_to_action' do
- @interaction.stubs(:sleep).raises(BreakError)
- @device.stubs(:read_pending_actions).returns(['message1', 'message2'])
- @interaction.expects(:respond_to_action).with('message1').once
- @interaction.expects(:respond_to_action).with('message2').once
- @interaction.start(:detached => true)
+
+ describe 'action handling' do
+
+ before do
+ @interaction.response_to(:mixer, :down) { @mixer_down = true }
+ @interaction.response_to(:mixer, :up) do |i,a|
+ sleep 0.001 # sleep to make "sure" :mixer :down has been processed
+ i.stop
+ end
+ @interaction.device.expects(:read_pending_actions).
+ at_least_once.
+ returns([
+ {
+ :timestamp => 0,
+ :state => :down,
+ :type => :mixer
+ },
+ {
+ :timestamp => 0,
+ :state => :up,
+ :type => :mixer
+ }
+ ])
+ end
+
+ it 'calls respond_to_action with actions from respond_to_action in blocking mode' do
+ erg = timeout(0.5) { @interaction.start }
+ assert erg, 'the actions weren\'t called'
+ assert @mixer_down, 'the mixer button wasn\'t pressed'
+ end
+
+ it 'calls respond_to_action with actions from respond_to_action in detached mode' do
+ @interaction.start(:detached => true)
+ erg = timeout(0.5) { while @interaction.active; sleep 0.01; end }
+ assert erg, 'there was a timeout'
+ assert @mixer_down, 'the mixer button wasn\'t pressed'
+ end
+
end
- # this is kinda greybox tested, since I couldn't come up with another way to test thread handling [thomas, 2010-01-24]
- context 'sleep' do
+ describe 'latency' do
- setup do
- @device.stubs(:read_pending_actions).returns([])
+ before do
+ @device = @interaction.device
+ @times = []
+ @device.instance_variable_set("@test_interaction_latency_times", @times)
+ def @device.read_pending_actions
+ @test_interaction_latency_times << Time.now.to_f
+ []
+ end
end
- should 'sleep with default latency of 0.001 when none given' do
- assert_raise BreakError do
- @interaction.expects(:sleep).with(0.001).raises(BreakError)
- @interaction.start
+ it 'sleeps with default latency of 0.001s when none given' do
+ timeout { @interaction.start }
+ assert @times.size > 1
+ @times.each_cons(2) do |a,b|
+ assert_in_delta 0.001, b - a, 0.01
end
end
- should 'sleep with given latency' do
- assert_raise BreakError do
- @interaction = Launchpad::Interaction.new(:latency => 4, :device => @device)
- @interaction.expects(:sleep).with(4).raises(BreakError)
- @interaction.start
+ it 'sleeps with given latency' do
+ @interaction = Launchpad::Interaction.new(:latency => 0.5, :device => @device)
+ timeout(0.55) { @interaction.start }
+ assert @times.size > 1
+ @times.each_cons(2) do |a,b|
+ assert_in_delta 0.5, b - a, 0.01
end
end
- should 'sleep with absolute value of given negative latency' do
- assert_raise BreakError do
- @interaction = Launchpad::Interaction.new(:latency => -3.1, :device => @device)
- @interaction.expects(:sleep).with(3.1).raises(BreakError)
- @interaction.start
+ it 'sleeps with absolute value of given negative latency' do
+ @interaction = Launchpad::Interaction.new(:latency => -0.1, :device => @device)
+ timeout(0.15) { @interaction.start }
+ assert @times.size > 1
+ @times.each_cons(2) do |a,b|
+ assert_in_delta 0.1, b - a, 0.01
end
end
- should 'not sleep when latency is 0' do
+ it 'does not sleep when latency is 0' do
@interaction = Launchpad::Interaction.new(:latency => 0, :device => @device)
- @interaction.expects(:sleep).never
- @interaction.start(:detached => true)
+ timeout(0.001) { @interaction.start }
+ assert @times.size > 1
+ @times.each_cons(2) do |a,b|
+ assert_in_delta 0, b - a, 0.1
+ end
end
end
- should 'reset the device after the loop' do
+ it 'resets the device after the loop' do
@interaction.device.expects(:reset)
@interaction.start(:detached => true)
@interaction.stop
end
- should 'raise NoOutputAllowedError on closed interaction' do
+ it 'raises NoOutputAllowedError on closed interaction' do
@interaction.close
- assert_raise Launchpad::NoOutputAllowedError do
+ assert_raises Launchpad::NoOutputAllowedError do
@interaction.start
end
end
end
- context 'stop' do
-
- should 'set active to false in blocking mode' do
- i = Launchpad::Interaction.new
- Thread.new do
- i.start
- end
- assert i.active
- i.stop
- assert !i.active
+ describe '#stop' do
+
+ before do
+ @interaction = Launchpad::Interaction.new
end
- should 'set active to false in detached mode' do
- i = Launchpad::Interaction.new
- i.start(:detached => true)
- assert i.active
- i.stop
- assert !i.active
+ it 'sets active to false in blocking mode' do
+ erg = timeout { @interaction.start }
+ refute erg, 'there was no timeout'
+ assert @interaction.active
+ @interaction.stop
+ assert !@interaction.active
end
- should 'be callable anytime' do
- i = Launchpad::Interaction.new
- i.stop
- i.start(:detached => true)
- i.stop
- i.stop
+ it 'sets active to false in detached mode' do
+ @interaction.start(:detached => true)
+ assert @interaction.active
+ @interaction.stop
+ assert !@interaction.active
end
- # this is kinda greybox tested, since I couldn't come up with another way to test thread handling [thomas, 2010-01-24]
- should 'call run and join on a running reader thread' do
- t = Thread.new {sleep}
- Thread.expects(:new).returns(t)
- t.expects(:run)
- t.expects(:join)
- i = Launchpad::Interaction.new
- i.start(:detached => true)
- i.stop
+ it 'is callable anytime' do
+ @interaction.stop
+ @interaction.start(:detached => true)
+ @interaction.stop
+ @interaction.stop
end
# this is kinda greybox tested, since I couldn't come up with another way to test tread handling [thomas, 2010-01-24]
- should 'raise pending exceptions in detached mode' do
+ it 'raises pending exceptions in detached mode' do
t = Thread.new {raise BreakError}
Thread.expects(:new).returns(t)
- i = Launchpad::Interaction.new
- i.start(:detached => true)
- assert_raise BreakError do
- i.stop
+ @interaction.start(:detached => true)
+ assert_raises BreakError do
+ @interaction.stop
end
end
end
- context 'response_to/no_response_to/respond_to' do
+ describe '#response_to/#no_response_to/#respond_to' do
- setup do
+ before do
@interaction = Launchpad::Interaction.new
end
- should 'call responses that match, and not others' do
+ it 'calls all responses that match, and not others' do
@interaction.response_to(:mixer, :down) {|i, a| @mixer_down = true}
@interaction.response_to(:all, :down) {|i, a| @all_down = true}
@interaction.response_to(:all, :up) {|i, a| @all_up = true}
@interaction.response_to(:grid, :down) {|i, a| @grid_down = true}
@interaction.respond_to(:mixer, :down)
@@ -239,11 +340,11 @@
assert @all_down
assert !@all_up
assert !@grid_down
end
- should 'not call responses when they are deregistered' do
+ it 'does not call responses when they are deregistered' do
@interaction.response_to(:mixer, :down) {|i, a| @mixer_down = true}
@interaction.response_to(:mixer, :up) {|i, a| @mixer_up = true}
@interaction.response_to(:all, :both) {|i, a| @all_down = a[:state] == :down}
@interaction.no_response_to(:mixer, :down)
@interaction.respond_to(:mixer, :down)
@@ -254,48 +355,101 @@
assert !@mixer_down
assert @mixer_up
assert !@all_down
end
- should 'not call responses registered for both when removing for one of both states' do
+ it 'does not call responses registered for both when removing for one of both states' do
@interaction.response_to(:mixer, :both) {|i, a| @mixer = true}
@interaction.no_response_to(:mixer, :down)
@interaction.respond_to(:mixer, :down)
assert !@mixer
@interaction.respond_to(:mixer, :up)
assert @mixer
end
- should 'remove other responses when adding a new exclusive response' do
+ it 'removes other responses when adding a new exclusive response' do
@interaction.response_to(:mixer, :both) {|i, a| @mixer = true}
@interaction.response_to(:mixer, :down, :exclusive => true) {|i, a| @exclusive_mixer = true}
@interaction.respond_to(:mixer, :down)
assert !@mixer
assert @exclusive_mixer
@interaction.respond_to(:mixer, :up)
assert @mixer
assert @exclusive_mixer
end
+
+ it 'allows for multiple types' do
+ @downs = []
+ @interaction.response_to([:up, :down], :down) {|i, a| @downs << a[:type]}
+ @interaction.respond_to(:up, :down)
+ @interaction.respond_to(:down, :down)
+ @interaction.respond_to(:up, :down)
+ assert_equal [:up, :down, :up], @downs
+ end
+
+ describe 'allows to bind to specific grid buttons' do
+
+ before do
+ @downs = []
+ @action = lambda {|i, a| @downs << [a[:x], a[:y]]}
+ end
+
+ it 'one specific grid button' do
+ @interaction.response_to(:grid, :down, :x => 4, :y => 2, &@action)
+ press_all @interaction
+ assert_equal [[4, 2]], @downs
+ end
+
+ it 'a complete row of grid buttons' do
+ @interaction.response_to(:grid, :down, :y => 2, &@action)
+ press_all @interaction
+ assert_equal [[0, 2], [1, 2], [2, 2], [3, 2], [4, 2], [5, 2], [6, 2], [7, 2]], @downs
+ end
+
+ it 'a complete column of grid buttons' do
+ @interaction.response_to(:grid, :down, :x => 3, &@action)
+ press_all @interaction
+ assert_equal [[3, 0], [3, 1], [3, 2], [3, 3], [3, 4], [3, 5], [3, 6], [3, 7]], @downs
+ end
+
+ it 'a complex range of grid buttons' do
+ @interaction.response_to(:grid, :down, :x => [1,[2]], :y => [1, 3..5], &@action)
+ press_all @interaction
+ assert_equal [[1, 1], [2, 1], [1, 3], [2, 3], [1, 4], [2, 4], [1, 5], [2, 5]], @downs
+ end
+
+ it 'a specific grid buttons, a column, a row, all grid buttons and all buttons' do
+ @interaction.response_to(:all, :down) {|i, a| @downs << [a[:x], a[:y], :all]}
+ @interaction.response_to(:grid, :down) {|i, a| @downs << [a[:x], a[:y], :grid]}
+ @interaction.response_to(:grid, :down, :x => 0) {|i, a| @downs << [a[:x], a[:y], :col]}
+ @interaction.response_to(:grid, :down, :y => 0) {|i, a| @downs << [a[:x], a[:y], :row]}
+ @interaction.response_to(:grid, :down, :x => 0, :y => 0, &@action)
+ press @interaction, :grid, :x => 0, :y => 0
+ assert_equal [[0, 0], [0, 0, :col], [0, 0, :row], [0, 0, :grid], [0, 0, :all]], @downs
+ end
+
+ end
end
- context 'regression tests' do
+ describe 'regression tests' do
- should 'not raise an exception when calling stop within a response in attached mode' do
- i = Launchpad::Interaction.new
- # strangely, you have to sleep 0.001 or do anything else before
- # calling i.stop - the ThreadError won't be thrown otherwise...
- i.response_to(:mixer, :down) {|i,a| sleep 0.001; i.stop}
- i.device.stubs(:read_pending_actions).returns([{
- :timestamp => 0,
- :state => :down,
- :type => :mixer
- }])
- assert_nothing_raised do
- Thread.new do
- i.start
- end.join
- end
+ it 'does not raise an exception or write an error to the logger when calling stop within a response in attached mode' do
+ log = StringIO.new
+ logger = Logger.new(log)
+ logger.level = Logger::ERROR
+ i = Launchpad::Interaction.new(:logger => logger)
+ i.response_to(:mixer, :down) {|i,a| i.stop}
+ i.device.expects(:read_pending_actions).
+ at_least_once.
+ returns([{
+ :timestamp => 0,
+ :state => :down,
+ :type => :mixer
+ }])
+ erg = timeout { i.start }
+ # assert erg, 'the actions weren\'t called'
+ assert_equal '', log.string
end
end
end