Permalink
Browse files

interaction's close stops interaction first

ability to detach interaction (non blocking  start method)
  • Loading branch information...
1 parent 5bdcc14 commit 2c6f4a7601451a780a623e6c7510715bcbaf1dd3 Thomas Jachmann committed Jan 24, 2010
Showing with 158 additions and 55 deletions.
  1. +44 −10 lib/launchpad/interaction.rb
  2. +114 −45 test/test_interaction.rb
@@ -53,7 +53,13 @@ def initialize(opts = nil)
end
# Closes the interaction's device - nothing can be done with the interaction/device afterwards.
+ #
+ # Errors raised:
+ #
+ # [Launchpad::NoInputAllowedError] when input is not enabled on the interaction's device
+ # [Launchpad::CommunicationError] when anything unexpected happens while communicating with the
def close
+ stop
@device.close
end
@@ -62,27 +68,55 @@ def closed?
@device.closed?
end
- # Starts interacting with the launchpad, blocking. Resets the device when
- # the interaction was properly stopped via stop.
+ # Starts interacting with the launchpad. Resets the device when
+ # the interaction was properly stopped via stop or close.
+ #
+ # Optional options hash:
+ #
+ # [<tt>:detached</tt>] <tt>true/false</tt>,
+ # whether to detach the interaction, method is blocking when +false+
+ # optional, defaults to +false+
#
# Errors raised:
#
# [Launchpad::NoInputAllowedError] when input is not enabled on the interaction's device
+ # [Launchpad::NoOutputAllowedError] when output is not enabled on the interaction's device
# [Launchpad::CommunicationError] when anything unexpected happens while communicating with the launchpad
- def start
+ def start(opts = nil)
+ opts = {
+ :detached => false
+ }.merge(opts || {})
@active = true
- while @active do
- @device.read_pending_actions.each {|action| respond_to_action(action)}
- sleep @latency unless @latency <= 0
+ @reader_thread ||= Thread.new do
+ begin
+ while @active do
+ @device.read_pending_actions.each {|action| respond_to_action(action)}
+ sleep @latency unless @latency <= 0
+ end
+ rescue Portmidi::DeviceError => e
+ raise CommunicationError.new(e)
+ ensure
+ @device.reset
+ end
end
- @device.reset
- rescue Portmidi::DeviceError => e
- raise CommunicationError.new(e)
+ @reader_thread.join unless opts[:detached]
end
- # Stops interacting with the launchpad and resets it.
+ # Stops interacting with the launchpad.
+ #
+ # Errors raised:
+ #
+ # [Launchpad::NoInputAllowedError] when input is not enabled on the interaction's device
+ # [Launchpad::CommunicationError] when anything unexpected happens while communicating with the
def stop
@active = false
+ if @reader_thread
+ # run (resume from sleep) and wait for @reader_thread to end
+ @reader_thread.run if @reader_thread.alive?
+ @reader_thread.join
+ @reader_thread = nil
+ end
+ nil
end
# Registers a response to one or more actions.
View
@@ -16,6 +16,11 @@ class TestInteraction < Test::Unit::TestCase
assert_equal 'device', Launchpad::Interaction.new(:device_name => 'device').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
+ end
+
should 'initialize device if given' do
assert_equal 'device', Launchpad::Interaction.new(:device => 'device').device
end
@@ -28,18 +33,17 @@ class TestInteraction < Test::Unit::TestCase
context 'close' do
- should 'close device' do
- interaction = Launchpad::Interaction.new(:device => device = Launchpad::Device.new)
- device.expects(:close)
+ should 'not be active' do
+ interaction = Launchpad::Interaction.new
+ interaction.start(:detached => true)
interaction.close
+ assert !interaction.active
end
- should 'craise NoInputAllowedError on subsequent accesses' do
+ should 'close device' do
interaction = Launchpad::Interaction.new(:device => device = Launchpad::Device.new)
+ device.expects(:close)
interaction.close
- assert_raise Launchpad::NoInputAllowedError do
- interaction.start
- end
end
end
@@ -57,27 +61,45 @@ class TestInteraction < Test::Unit::TestCase
context 'start' do
- # this is kinda greybox tested, since I couldn't come up with another way to test a loop [thomas, 2009-11-11]
-
setup do
@interaction = Launchpad::Interaction.new(:device => @device = Launchpad::Device.new)
end
- context 'up until read_pending_actions' do
-
- setup do
- @device.stubs(:read_pending_actions).raises(BreakError)
- end
-
- should 'set active to true' do
- begin
- @interaction.start
- fail 'should raise BreakError'
- rescue BreakError
- assert @interaction.active
- end
+ teardown do
+ 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
+ assert @interaction.active
+ end
+
+ should 'set active to true in detached mode' do
+ @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
+ 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)
end
should 'raise CommunicationError when Portmidi::DeviceError occurs' do
@@ -87,71 +109,118 @@ class TestInteraction < Test::Unit::TestCase
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
- begin
- @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
- fail 'should raise BreakError'
- rescue BreakError
- end
+ @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)
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
setup do
@device.stubs(:read_pending_actions).returns([])
end
should 'sleep with default latency of 0.001 when none given' do
- begin
+ assert_raise BreakError do
@interaction.expects(:sleep).with(0.001).raises(BreakError)
@interaction.start
- fail 'should raise BreakError'
- rescue BreakError
end
end
should 'sleep with given latency' do
- begin
+ assert_raise BreakError do
@interaction = Launchpad::Interaction.new(:latency => 4, :device => @device)
@interaction.expects(:sleep).with(4).raises(BreakError)
@interaction.start
- fail 'should raise BreakError'
- rescue BreakError
end
end
should 'sleep with absolute value of given negative latency' do
- begin
+ assert_raise BreakError do
@interaction = Launchpad::Interaction.new(:latency => -3.1, :device => @device)
@interaction.expects(:sleep).with(3.1).raises(BreakError)
@interaction.start
- fail 'should raise BreakError'
- rescue BreakError
end
end
- should 'not sleep when latency is <= 0' # TODO don't know how to test this [thomas, 2009-11-11]
+ should 'not sleep when latency is 0' do
+ @interaction = Launchpad::Interaction.new(:latency => 0, :device => @device)
+ @interaction.expects(:sleep).never
+ @interaction.start(:detached => true)
+ end
end
- should 'reset the device after the loop' # TODO don't know how to test this [thomas, 2009-11-11]
+ should 'reset the device after the loop' do
+ @interaction.device.expects(:reset)
+ @interaction.start(:detached => true)
+ @interaction.stop
+ end
+
+ should 'raise NoOutputAllowedError on closed interaction' do
+ @interaction.close
+ assert_raise Launchpad::NoOutputAllowedError do
+ @interaction.start
+ end
+ end
end
context 'stop' do
- should 'set active to false' 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
+ end
+
+ should 'set active to false in detached mode' do
i = Launchpad::Interaction.new
- i.instance_variable_set('@active', true)
+ i.start(:detached => true)
assert i.active
i.stop
assert !i.active
end
+ should 'be callable anytime' do
+ i = Launchpad::Interaction.new
+ i.stop
+ i.start(:detached => true)
+ i.stop
+ i.stop
+ 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
+ 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
+ t = Thread.new {raise BreakError}
+ Thread.expects(:new).returns(t)
+ i = Launchpad::Interaction.new
+ i.start(:detached => true)
+ assert_raise BreakError do
+ i.stop
+ end
+ end
+
end
context 'response_to/no_response_to/respond_to' do

0 comments on commit 2c6f4a7

Please sign in to comment.