Skip to content

Commit

Permalink
Merge branch 'feature/testing_with_timidity' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
Sam Rose committed Apr 17, 2012
2 parents 41d68d3 + 11c5f4e commit 7b19444
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 45 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Expand Up @@ -7,3 +7,4 @@ rvm:
before_install:
- sudo apt-get update
- sudo apt-get install libasound2-dev
- sudo apt-get install timidity
1 change: 1 addition & 0 deletions Gemfile
Expand Up @@ -21,4 +21,5 @@ end
group :testing do
gem 'rspec'
gem 'rake'
gem 'ruby-ogginfo'
end
18 changes: 4 additions & 14 deletions Gemfile.lock
@@ -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)
Expand All @@ -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
84 changes: 84 additions & 0 deletions spec/README.md
@@ -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.
37 changes: 36 additions & 1 deletion spec/server_spec.rb
Expand Up @@ -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"
Expand Down Expand Up @@ -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
13 changes: 0 additions & 13 deletions spec/spec_helper.rb
Expand Up @@ -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
55 changes: 38 additions & 17 deletions spec/support/server.rb
@@ -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
49 changes: 49 additions & 0 deletions spec/support/test_server.rb
@@ -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
53 changes: 53 additions & 0 deletions spec/support/timidity.rb
@@ -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.