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