Permalink
Browse files

implemented an integration testing framework

  • Loading branch information...
1 parent 07cd3cc commit 0db73d877893336ad36f03c67ad4644ba3fc28ac Frederik Fix committed May 16, 2009
View
@@ -12,6 +12,15 @@ Rake::TestTask.new(:test) do |t|
t.verbose = true
end
+
+desc 'Run the integration tests'
+Rake::TestTask.new(:integration) do |t|
+ t.libs << 'lib'
+ t.pattern = 'integration/tests/*_test.rb'
+ t.verbose = true
+end
+
+
desc 'Generate documentation for the loopy_workling plugin.'
Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
@@ -0,0 +1,87 @@
+== Integration tests ==
+
+This directory contains integration tests for Workling. These test are supposed to verify the the front to end operation, including starting the workling daemon and interacting with queue servers etc. The tests are specified in a setup independent way and then run for every setup.
+
+=== Running Tests ===
+
+In order to run the integration tests use:
+
+ rake integration
+
+
+=== Adding Tests ===
+
+Integration tests work pretty much like normal tests, except that they are run for every setup defined. Add a test file in the integration/tests directory ending in _test.rb and then use the following construct to have your test run in all defined setups:
+
+ $setup_loader.each_setup do |setup|
+
+ # to make the output more readable add the setup.name to the name of your test
+ context "Test name for #{setup.name}" do
+
+ # make sure you have the following in your test
+ setup do
+ setup.setup_test
+ end
+
+ teardown do
+ setup.teardown_test
+ end
+
+
+ specify "should shoot for the moon" do
+ # test code goes here
+ end
+
+ end
+
+ end
+
+Any workers that are need for the integration tests should go into the integration/workers directory.
+
+The environment for the tests is as follows:
+ * There is a tmp directory which can be used for the tests. The setup.tmp_directory method provides access to this
+ * This tmp_directory is cleaned out before and after every tests by the setup and teardown methods
+ * The queue server and the daemon are spun up and down for every test in order to ensure a clean environment, if the current setup requires the daemon
+ * Rails will NOT be booted for the daemon, so the Active* goodness will not be available in the workers
+
+=== Adding a setup ===
+
+Different deployment options are encapsulated using different IntegrationSetup classes. Each setup has a name and some configuration options. They are specified by ruby files in the integration/setups directory. Note that a file can of course contain multiple setups but the names should be unique.
+
+A setup is specified using the following way:
+
+ $setup_loader.register("some name") do |s|
+
+ s.run_daemon # if this is present then the daemon will be spun up, all runners except for the Spawn and BackgroundJob runners need this setting
+ s.client ClientClass # specify which client class is used, needs to be a class not a string
+ s.invoker InvokerClass # specify which invoker class is used, needs to be a class not a string
+ # Both settings are used for configuring both the dispatching side as well as the daemon
+
+
+ # the config setting is optional, if it is present it should give a hash similar to what the parsed YAML
+ # would be. If this is given then the workling.yml file will not be parsed on the dispatch side
+ s.config do
+ { :listens_on => "localhost:22122" }
+ end
+
+ # the guard block makes sure that the required prerequisites are present
+ # you should make sure that the required queue server is installed and so on
+ # in order to prevent spurious failures
+ s.guard do
+ Workling::Clients::MemcacheQueueClient.installed?
+ end
+
+ # This is run for every specify block and it should spin up the queue server if required.
+ # if the block is given it will be run before the daemon is launched
+ s.setup_each do
+ `starling -P #{s.tmp_directory}/starling.pid -d`
+ end
+
+ # This is run for every specify block and it should spin down the queue server if it was started.
+ # if it is given it will be called after the daemon is stopped
+ s.teardown_each do
+ `kill #{File.read("#{s.tmp_directory}/starling.pid")}`
+ end
+
+ end
+
@@ -0,0 +1,21 @@
+require 'rubygems'
+
+gem 'activesupport'
+require 'active_support'
+
+require 'test/spec'
+
+RAILS_ENV = "test"
+RAILS_ROOT = File.dirname(__FILE__) + "/.." # fake the rails root directory.
+RAILS_DEFAULT_LOGGER = ActiveSupport::BufferedLogger.new(File.dirname(__FILE__) + "/../test.log")
+
+require File.join(File.dirname(__FILE__), "../lib/workling")
+
+# worklings are in here.
+Workling.load_path = [File.join(File.dirname(__FILE__), "workers/*.rb")]
+Workling::Discovery.discover!
+
+require File.dirname(__FILE__) + '/support/integration_setup_loader'
+
+$setup_loader = IntegrationSetupLoader.build File.join(File.dirname(__FILE__), "setups")
+$setup_loader.load!
@@ -0,0 +1,23 @@
+$setup_loader.register("simple starling") do |s|
+
+ s.run_daemon
+ s.client Workling::Clients::MemcacheQueueClient
+ s.invoker Workling::Remote::Invokers::BasicPoller
+
+ s.config do
+ { :listens_on => "localhost:22122" }
+ end
+
+ s.guard do
+ Workling::Clients::MemcacheQueueClient.installed?
+ end
+
+ s.setup_each do
+ `starling -P #{s.tmp_directory}/starling.pid -d`
+ end
+
+ s.teardown_each do
+ `kill #{File.read("#{s.tmp_directory}/starling.pid")}`
+ end
+
+end
@@ -0,0 +1,9 @@
+$setup_loader.register("spawn") do |s|
+
+ s.dispatcher Workling::Remote::Runners::SpawnRunner
+
+ s.guard do
+ Workling::Remote::Runners::SpawnRunner.installed?
+ end
+
+end
@@ -0,0 +1,84 @@
+class IntegrationSetup
+
+ attr_reader :name
+
+ def self.tmp_directory
+ File.join(File.dirname(__FILE__), "../tmp")
+ end
+
+ def tmp_directory
+ self.class.tmp_directory
+ end
+
+ def self.base_directory
+ File.join(File.dirname(__FILE__), "../..")
+ end
+
+ def base_directory
+ self.class.base_directory
+ end
+
+
+ def initialize(name, options={})
+ @name = name
+ @options = options
+ end
+
+ def can_run?
+ if @options[:guard]
+ @options[:guard].call
+ else
+ true
+ end
+ end
+
+ def setup_test
+ clean_tmp_directory
+ @options[:setup_each] && @options[:setup_each].call
+ start_daemon if @options[:run_daemon]
+ setup_environment
+ end
+
+ def teardown_test
+ end_daemon if @options[:run_daemon]
+ @options[:teardown_each] && @options[:teardown_each].call
+ clean_tmp_directory
+ end
+
+ def setup_group
+
+ end
+
+ def teardown_group
+
+ end
+
+ private
+ def clean_tmp_directory
+ Dir.glob(File.join(tmp_directory, "*")).each do |f|
+ File.delete f
+ end
+ end
+
+ def setup_environment
+ if @options[:config]
+ Workling.config = @options[:config].call
+ end
+
+ if @options[:dispatcher]
+ Workling::Remote.dispatcher = @options[:dispatcher].new
+ else
+ Workling::Remote.dispatcher = Workling::Remote::Runners::ClientRunner.new
+ Workling::Remote.dispatcher.client = @options[:client].new
+ end
+ end
+
+ def start_daemon
+ `ruby #{base_directory}/bin/workling_client start -a integration_workling -d #{tmp_directory} -- -n -i #{@options[:invoker]} -c #{@options[:client]} -l #{base_directory}/integration/workers/*.rb`
+ end
+
+ def end_daemon
+ `ruby #{base_directory}/bin/workling_client stop -a integration_workling -d #{tmp_directory}`
+ end
+
+end
@@ -0,0 +1,43 @@
+require File.dirname(__FILE__) + '/integration_setup_recorder'
+
+class IntegrationSetupLoader
+
+ def self.build(*args)
+ @instance ||= new(*args)
+ end
+
+ def initialize(setup_dir)
+ @setup_dir = setup_dir
+ @setups = []
+ @initialized = false
+ end
+
+ def load!
+ # only setup once
+ return if @initialized
+ @initialized = true
+
+ Dir.glob(File.join(@setup_dir, "*.rb")).each do |f|
+ require File.join(File.dirname(f), File.basename(f, ".rb"))
+ end
+ end
+
+ def register(name, &block)
+ setup_recorder = IntegrationSetupRecorder.new(name)
+ yield setup_recorder
+ @setups << setup_recorder.build_setup
+ end
+
+
+ def each_setup(&block)
+ @setups.select(&:can_run?).each do |setup|
+ begin
+ setup.setup_group
+ yield setup
+ ensure
+ setup.teardown_group
+ end
+ end
+ end
+
+end
@@ -0,0 +1,60 @@
+require File.dirname(__FILE__) + '/integration_setup'
+
+class IntegrationSetupRecorder
+
+ def initialize(name)
+ @name = name
+ end
+
+ # recorders
+ def guard(&block)
+ @guard = block
+ end
+
+ def setup_each(&block)
+ @setup_each = block
+ end
+
+ def teardown_each(&block)
+ @teardown_each = block
+ end
+
+ def run_daemon
+ @run_daemon = true
+ end
+
+ def dispatcher(dispatcher)
+ @dispatcher = dispatcher
+ end
+
+ def client(client)
+ @client = client
+ end
+
+ def invoker(invoker)
+ @invoker = invoker
+ end
+
+ def config(&block)
+ @config = block
+ end
+
+ # convenience methods
+ def tmp_directory
+ IntegrationSetup.tmp_directory
+ end
+
+ # output
+ def build_setup
+ IntegrationSetup.new @name,
+ :guard => @guard,
+ :config => @config,
+ :setup_each => @setup_each,
+ :teardown_each => @teardown_each,
+ :run_daemon => @run_daemon,
+ :dispatcher => @dispatcher,
+ :client => @client,
+ :invoker => @invoker
+ end
+
+end
@@ -0,0 +1,26 @@
+require File.dirname(__FILE__) + '/../integration_helper'
+
+$setup_loader.each_setup do |setup|
+
+ context "Basic Operation for #{setup.name}" do
+
+ setup do
+ setup.setup_test
+ end
+
+ teardown do
+ setup.teardown_test
+ end
+
+
+ specify "should execute async operation" do
+ BasicOperationWorker.async_do_work(:token => "my magic token")
+ sleep(2) # this is not clean, but we need to wait for the async call to finish
+
+ File.exists?(File.join(setup.tmp_directory, "basic_operation.output")).should == true
+ File.read(File.join(setup.tmp_directory, "basic_operation.output")).should == "my magic token"
+ end
+
+ end
+
+end
@@ -0,0 +1,14 @@
+class BasicOperationWorker < Workling::Base
+
+ def do_work(input)
+ File.open(output_file_name, "w") do |f|
+ f.write(input[:token])
+ end
+ end
+
+ private
+ def output_file_name
+ File.join(File.dirname(__FILE__), "../tmp/basic_operation.output")
+ end
+
+end
Oops, something went wrong. Retry.

0 comments on commit 0db73d8

Please sign in to comment.