------ art_39344_32700503.1160422387473 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit Content-Disposition: inline Hi Joe, This looks really cool. I will have to see how it can integrate with TESTify, my test management system. It seems like a cool way to do things, espically in the corporate or many person open-source project situations that TESTify is being created for. -- Chris On 10/9/06, Joe Hosteny iv <jhosteny / gmail.com> wrote: > > Hi all, > > I have been working on some code to assist with distributed unit testing > using Test::Unit and Rinda. I thought I'd post it here assuming that > someone else might find it interesting or useful. It's a bit raw, and > I'm still working out some bugs with unclean shutdowns of the test > servers. Also, it's not documented (yet), but it's only about 240 lines > of code. > > There are easier ways of doing this, of course, but I had a few > requirements that caused me to write it this way: > > 1) Distribute tests to the test servers on an individual test method > basis > 2) Avoid (as much as possible) having to rewrite any of the Test::Unit > code via method aliasing. > > You'll have to run a ring server - see ringserver.rb from Eric Hodel's > site at http://segment7.net/projects/ruby/drb/rinda/ringserver.html. > Also, I did not provide the 'attribute_accessors' file, since that is > just like the one in the rails support package (except that is modified > to be used in a Module instead of Class). The rest of the files are > included inline below. Here is an explanation of what to do with each: > > service.rb - > > This file continas definitions for producer/consumer classes for the > distributed test service, which is shared via a tuple space. > > distributed.rb - > > This file contains mixins for Test::Unit::TestCase and > Test::Unit::TestSuite that enable them to use the distributed service. > > server.rb - > > Run this on every machine that will be given unit tests to run. > > tests.rb - > > This is a sample unit test file > > test.rb - > > This is a sample master script, which is run as 'ruby test.rb -d > tests.rb.' If you run 'ruby test.rb tests.rb,' the tests are run > locally. > > Regards, > Joe Hosteny > jhosteny at gmail dot com > > --service.rb-- > require 'rinda/ring' > require 'rinda/tuplespace' > require 'rinda/rinda' > > def log *args > $stdout.write "(#{Thread.current}) " > puts *args > $stdout.flush > end > > module Rinda > class RingFinger > # Change this to your local network broadcast netmask > @@broadcast_list.push("192.168.1.255") > end > end > > module Service > class Base > def initialize(name) > @name ame > DRb.start_service > log "Started DRb on URI #{DRb.uri}" > Rinda::RingFinger.primary > end > > def consumer? > respond_to? :consume > end > > def method_missing(meth, *args) > ts hread.current[:tuplespace][2] > ts inda::TupleSpaceProxy.new(ts) if consumer? > ts.send(meth, *args) > end > end > > class Producer < Base > def initialize(name) > super > ts inda::TupleSpace.new > name #{@name}:#{DRb.uri}" > tuple inda::RingProvider.new(@name.to_sym, ts, name).provide > Thread.current[:tuplespace] > Rinda::RingFinger.primary.read(tuple) > trap("EXIT") do > Rinda::RingFinger.primary.take(Thread.current[:tuplespace]) > end > end > end > > class Consumer < Base > def consume > tuple :name, @name.to_sym, nil, nil] > Thread.current[:tuplespace] > Rinda::RingFinger.primary.take(tuple) > log "Got tuplespace from URI: > #{Thread.current[:tuplespace][2].__drburi}" > begin > yield self > ensure > Rinda::RingFinger.primary.write(Thread.current[:tuplespace]) > end > end > end > end > > --distributed.rb-- > require 'test/unit' > require 'test/unit/testresult' > require 'attribute_accessors' > require 'service' > > module DistributedTestCase > module ClassMethods > @@service il > mattr_accessor :service > > @@file il > mattr_accessor :file > > module Run > end > > def start_client > @@service ervice::Consumer.new('DistributedTest') > end > def start_server > @@service ervice::Producer.new('DistributedTest') > loop do > log "Waiting to take test" > file, name, meth, oid (@@service.take([:test, nil]).last) > log "Loading #{name}::#{meth} in file #{file}" > load(file) > klass il > i > ObjectSpace.each_object do |obj| > if (obj.class Class and obj.to_s name) > klass bj > break > end > i + > end > log "Checked #{i} objects" > begin > test lass.new(meth) > log "Running #{name}::#{meth} in file #{file})" > test.run(Test::Unit::TestResultProxy.new(@@service, oid)) > log "Finished running #{name}::#{meth} in file #{file}" > rescue e > @@service.write([:result, oid, :exception, e]) > end > end > end > > def inherited(base) > caller[0] /(.+?):.*/ > @@file ile.expand_path($1) > end > end > > class << self > def included(base) > base.extend(ClassMethods) > base.class_eval do > alias_method :run_original, :run > alias_method :run, :run_distributed > end > end > end > > def run_distributed(result) > if ClassMethods.service.consumer? > th hread.new do > log "New thread" > ClassMethods.service.consume do |srv| > oid ethod(method_name).object_id > log "Dispatching test #{self.class.to_s}::#{method_name} > (#{oid})" > srv.write [:test, [ClassMethods.file, self.class.to_s, > method_name, oid]] > log "Waiting for result from > #{self.class.to_s}::#{method_name}" > loop do > tuple :result, oid, nil, nil] > tuple rv.take(tuple) > args, method uple.pop, tuple.pop > log "Test #{self.class.to_s}::#{method_name} called > #{method}" > if method :exception > raise args.class, "#{args.message}\n\t(remote) > #{args.backtrace.join("\n\t(remote) ")}\n" > end > if %W(add_failure add_error).include? method.to_s > klass est::Unit::Error > klass est::Unit::Failure if method.to_s /failure/ > result.send(method, klass.new(*args)) > else > result.send(method) > end > break if method :add_run > end > end > log "Thread exiting" > end > callcc do |cc| > throw :new_thread, [th, cc] > end > else > run_original(result) do |s,n| end > end > end > end > > module DistributedTestSuite > class << self > def included(base) > base.class_eval do > alias_method :run_original, :run > alias_method :run, :run_distributed > end > end > end > > def run_distributed(result, &block) > threads ] > th, cc catch(:new_thread) do > run_original(result, &block) > nil > end > if th > threads << th > cc.call > end > threads.each { |th| th.join } > end > end > > module Test > module Unit > class TestSuite > include DistributedTestSuite > end > class TestCase > include DistributedTestCase > end > class TestResultProxy > def initialize(server, oid) > @server erver > @oid id > end > > def method_missing(name, *args) > name ame.id2name > if name /add_(.*)/ > if %W(failure error).include? $1 > args rgs[0] > if $1 /failure/ > args args.test_name, args.location, args.message] > else > args args.test_name, args.exception] > end > end > @server.write([:result, @oid, name.to_sym, args]) > end > end > end > end > end > > --server.rb-- > #!/bin/env ruby > require 'optparse' > require 'distributed' > > Test::Unit::TestCase.start_server > > --tests.rb-- > require 'test/unit' > > class TC_MyTest < Test::Unit::TestCase > def setup > puts "in setup" > end > > def teardown > puts "in teardown" > end > > def test_it > assert(false, 'Assertion was false.') > end > > def test_pass > assert(true, 'Assertion was true.') > end > end > > --test.rb-- > #!/bin/env ruby > require 'optparse' > require 'distributed' > Test::Unit::TestCase.start_client > require ARGV.shift > > > -- > Posted via http://www.ruby-forum.com/. > > -- Chris Carter concentrationstudios.com brynmawrcs.com ------ art_39344_32700503.1160422387473--