Permalink
Browse files

Merge branch 'feature/testing_with_timidity' into develop

  • Loading branch information...
2 parents 41d68d3 + 11c5f4e commit 7b194447253d8c7f9c05d859f504335364872191 @samwho committed Apr 17, 2012
Showing with 266 additions and 45 deletions.
  1. +1 −0 .travis.yml
  2. +1 −0 Gemfile
  3. +4 −14 Gemfile.lock
  4. +84 −0 spec/README.md
  5. +36 −1 spec/server_spec.rb
  6. +0 −13 spec/spec_helper.rb
  7. +38 −17 spec/support/server.rb
  8. +49 −0 spec/support/test_server.rb
  9. +53 −0 spec/support/timidity.rb
View
@@ -7,3 +7,4 @@ rvm:
before_install:
- sudo apt-get update
- sudo apt-get install libasound2-dev
+ - sudo apt-get install timidity
View
@@ -21,4 +21,5 @@ end
group :testing do
gem 'rspec'
gem 'rake'
+ gem 'ruby-ogginfo'
end
View
@@ -1,21 +1,14 @@
GEM
remote: http://rubygems.org/
specs:
- alsa-rawmidi (0.2.14)
- ffi (>= 1.0)
coderay (0.9.8)
colored (1.2)
diff-lcs (1.1.3)
eventmachine (0.12.10)
- ffi (1.0.11)
- ffi-coremidi (0.1.7)
- ffi (>= 1.0)
json (1.6.5)
method_source (0.6.7)
ruby_parser (>= 2.3.1)
- midi-jruby (0.0.12)
- midi-winmm (0.1.10)
- ffi (>= 1.0)
+ midiator (0.5.1)
pry (0.9.7.4)
coderay (~> 0.9.8)
method_source (~> 0.6.7)
@@ -30,26 +23,23 @@ GEM
rspec-expectations (2.8.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.8.0)
+ ruby-ogginfo (0.6.8)
ruby_cowsay (0.1.0)
ruby_parser (2.3.1)
sexp_processor (~> 3.0)
sexp_processor (3.0.8)
slop (2.1.0)
- unimidi (0.3.3)
- ffi-coremidi
PLATFORMS
ruby
DEPENDENCIES
- alsa-rawmidi
colored
eventmachine
json
- midi-jruby
- midi-winmm
+ midiator
pry
rake
rspec
+ ruby-ogginfo
ruby_cowsay
- unimidi
View
@@ -0,0 +1,84 @@
+# Jarvis Testing
+
+This is where all of the Jarvis tests and testing framework lives. There are a
+number of files that will help support writing new tests, including RSpec shared
+contexts and rake tasks.
+
+## Running Tests
+
+Tests are run using the standard rake command:
+
+ $ rake
+
+This will fire off an RSpec testing suite with the command line options -cfs
+(coloured output and specdoc format).
+
+## Shared Contexts
+
+Jarvis has two (technically three but two are usually used in conjunction with
+each other) shared contexts that help with testing.
+
+### "server" and "timidity"
+
+This context is aimed at making writing tests that communicate with an external
+Jarvis process really, really easy.
+
+If you include these lines (in this order) in your RSpec describe block:
+
+ include_context 'timidity'
+ include_context 'server'
+
+You will get given a variety of helper methods and some before and after hooks
+will automatically run for you. First, a timidity server will be run in the
+background. It won't make any sound, instead it will output everything to an Ogg
+Vorbis file. This ogg file can be accessed using the `test_ogg` helper method.
+
+The `ogginfo` gem is used to extract information about the ogg file.
+Documentation for `ogginfo` can be found
+[here](https://github.com/moumar/ruby-ogginfo). If `test_ogg` returns nil it
+means that either the ogg file does not exist or timidity never wrote to it,
+which indicates that communication between Jarvis and timidity is not working.
+
+When timidity is up and running, a Jarvis process will be spawned. There are
+calls to `sleep` involved in booting Jarvis and timidity to give them time to
+load up.
+
+When both of these are running, the content of your test is run. There are two
+very useful helper methods that will aid in your test writing: `send_command`
+and `last_response`. Both of them are quite self explanatory. `send_command`
+will take a string and send it to the server and return the server response.
+`last_response` is a way of accessing that response numerous times.
+
+Here's an example:
+
+``` ruby
+ describe "Server" do
+ it "should respond to start" do
+ send_command "start"
+ last_response.should include('successfully')
+ end
+ end
+```
+
+When your test has finished executing the Jarvis and timidity processes will be
+killed.
+
+**Note:** This context is quite noisy. All of the server boot up logs are put
+into stdout for debugging purposes (in case something goes wrong, you will want
+to look at the server logs). Test output is often very busy with this context.
+
+### test_server
+
+The "test_server" context will give you exactly the same `send_command` and
+`last_response` helper methods as the "server" context described above, but it
+will not spawn external processes.
+
+This context will monkey patch the MusicServer class so that it does not try and
+send any data to clients. Instead, it stores that data and makes it accessible
+through the `last_response` helper.
+
+Tests written with this context will look almost exactly the same as tests
+written with the "server" context. The only difference is that there is no
+timidity server running and, therefore, no ogg file to test against. This
+context is just for doing granular tests against the server object that doesn't
+require any interprocess communication.
View
@@ -2,7 +2,7 @@
# Tell RSpec that we're in the server context. This will give us access to an
# instance of MusicServer in the @jarvis instance variable and access to some
# specific helper methods.
- include_context 'server'
+ include_context 'test_server'
it 'responds to start' do
send_command "start"
@@ -64,3 +64,38 @@
last_response.should include('ERROR')
end
end
+
+# This following testing suite fires up two external processes for each test.
+# This is an inherently heavy and time consuming process so there are not many
+# specs in it.
+#
+# The idea here is to test that all of the interprocess communication works. The
+# timidity process is told to
+describe "External Server Connection" do
+ include_context 'timidity'
+ include_context 'server'
+
+ it 'should communicate with timidity' do
+ send_command 'start'
+
+ stop_jarvis
+ stop_timidity
+
+ test_ogg.should_not be_nil
+ test_ogg.length.should be > 0
+ end
+
+ Jarvis::Generators::NoteGenerator.generators.each do |generator|
+ it "#{generator} should communicate with timidity" do
+ send_command "load #{generator}"
+ send_command 'start'
+ send_command 'stop'
+
+ stop_jarvis
+ stop_timidity
+
+ test_ogg.should_not be_nil
+ test_ogg.length.should be > 0
+ end
+ end
+end
View
@@ -6,16 +6,3 @@
# We don't want to hear what the generators have to say unless we explicitly
# state otherwise.
Jarvis::Generators::NoteGenerator.stdout = File.open('/dev/null', 'w')
-
-# Because we aren't running the server inside event machine, we need to override
-# the send_data method and keep track of the data that has been sent in the
-# current request.
-class Jarvis::MusicServer
- def sent_data
- @sent_data ||= []
- end
-
- def send_data data
- sent_data << data.to_s
- end
-end
View
@@ -1,32 +1,53 @@
+require 'socket'
+
shared_context 'server' do
- # Helper method to send a command to the Jarvis server.
+ @@jarvis_host = 'localhost'
+ @@jarvis_port = 1337
+
+ # Starts up the external jarvis process and waits for it to boot. Then it
+ # initiates a connection to the jarvis process under the @@socket variable.
+ #
+ # Commands can be sent to this jarvis instance with the send_command method.
+ def start_jarvis
+ @@jarvis_pid = fork do
+ exec "#{Jarvis::ROOTDIR}/bin/jarvis"
+ end
+
+ sleep(1)
+ @@socket = TCPSocket.new @@jarvis_host, @@jarvis_port
+ end
+
+ # Sends a command to the jarvis server that's running and returns the string
+ # response from the server.
def send_command command
- @jarvis.receive_data command
- @last_response = @jarvis.sent_data.last
+ @@socket.print command
+ @@last_response = @@socket.recv 4096
end
- # Helper method to check the value of the last command sent.
+ # Refers to the response from the last command sent to jarvis.
def last_response
- @last_response
+ @@last_response
end
- before :each do
- # Set option defaults and override testing to true
- Jarvis.options = Jarvis.option_defaults
- Jarvis.options[:logfile] = '/dev/null'
- Jarvis.options[:testing] = true
- Jarvis.log = Jarvis.default_logger
+ # Stops the external jarvis process and closes the socket connection to it.
+ def stop_jarvis
+ if @@jarvis_pid
+ @@socket.close if @@socket
+ Process.kill('SIGINT', @@jarvis_pid)
+ Process.wait(@@jarvis_pid)
+ @@jarvis_pid = nil
+ end
+ end
- # Reset the commands array. This stops duplicate command errors.
- Jarvis::Command.reset
- @jarvis = Jarvis::MusicServer.new nil
+ # RSpec hook to start jarvis before every test case in this context.
+ before :each do
@last_response = nil
+ start_jarvis
end
+ # RSpec hook to stop jarvis after every test case in this context.
after :each do
- @jarvis.unbind
- @jarvis = nil
+ stop_jarvis
end
-
end
@@ -0,0 +1,49 @@
+# This shared context sets up all of the appropriate methods and monkey patches
+# that are needed to create and use a MusicServer object without running it
+# through EventMachine.
+
+# Because we aren't running the server inside event machine, we need to override
+# the send_data method and keep track of the data that has been sent in the
+# current request.
+class Jarvis::MusicServer
+ def sent_data
+ @sent_data ||= []
+ end
+
+ def send_data data
+ sent_data << data.to_s
+ end
+end
+
+shared_context 'test_server' do
+ # Helper method to send a command to the Jarvis server.
+ def send_command command
+ @jarvis.receive_data command
+ @last_response = @jarvis.sent_data.last
+ end
+
+ # Helper method to check the value of the last command sent.
+ def last_response
+ @last_response
+ end
+
+ before :each do
+ # Set option defaults and override testing to true
+ Jarvis.options = Jarvis.option_defaults
+ Jarvis.options[:logfile] = '/dev/null'
+ Jarvis.options[:testing] = true
+ Jarvis.log = Jarvis.default_logger
+
+ # Reset the commands array. This stops duplicate command errors.
+ Jarvis::Command.reset
+
+ @jarvis = Jarvis::MusicServer.new nil
+ @last_response = nil
+ end
+
+ after :each do
+ @jarvis.unbind
+ @jarvis = nil
+ end
+
+end
View
@@ -0,0 +1,53 @@
+require 'ogginfo'
+require 'fileutils'
+
+shared_context "timidity" do
+ # Starts up timidity in an external process. It is told to output all sounds
+ # in ogg vorbis format to a test.ogg file defined by the test_ogg_path method
+ # of this context.
+ def start_timidity
+ @@timidity_pid = fork do
+ exec "timidity -iA -Ov -o #{test_ogg_path}"
+ end
+
+ sleep(1)
+ end
+
+ # Stops the external timidity process.
+ def stop_timidity
+ if @@timidity_pid
+ Process.kill('SIGINT', @@timidity_pid)
+ Process.wait(@@timidity_pid)
+ @@timidity_pid = nil
+ end
+ end
+
+ # The absolute path to the test.ogg file that we are telling timidity to write
+ # to.
+ def test_ogg_path
+ Jarvis::ROOTDIR + '/spec/data/test.ogg'
+ end
+
+ # Gets the designated test.ogg file from inside spec/data. If the ogg file is
+ # empty or not there, nil will be returned. This is useful for asserting that
+ # notes actually made it to the server.
+ def test_ogg
+ begin
+ OggInfo.open(test_ogg_path)
+ rescue Exception => e
+ nil
+ end
+ end
+
+ # Sets up an rspec hook to start the timidity server before each spec
+ before :each do
+ start_timidity
+ end
+
+ # Sets up an rspec hook to stop the timidity process after each spec
+ after :each do
+ stop_timidity
+
+ FileUtils.rm test_ogg_path
+ end
+end

0 comments on commit 7b19444

Please sign in to comment.