Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

interaction's close stops interaction first

ability to detach interaction (non blocking  start method)
  • Loading branch information...
commit 2c6f4a7601451a780a623e6c7510715bcbaf1dd3 1 parent 5bdcc14
@thomasjachmann authored
Showing with 158 additions and 55 deletions.
  1. +44 −10 lib/launchpad/interaction.rb
  2. +114 −45 test/test_interaction.rb
View
54 lib/launchpad/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
159 test/test_interaction.rb
@@ -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,18 +109,16 @@ 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
@@ -106,52 +126,101 @@ class TestInteraction < Test::Unit::TestCase
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
Please sign in to comment.
Something went wrong with that request. Please try again.