# Distributed testing with Test::Unit and Rinda

**URL:** <https://www.ruby-forum.com/t/distributed-testing-with-test-unit-and-rinda/71514>\
**Category:** Ruby\
**Created:** [October 9, 2006, 4:38pm UTC](https://www.ruby-forum.com/t/distributed-testing-with-test-unit-and-rinda/71514 "2006-10-09T16:38:38Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![jhosteny](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/bbe5ce/32.png) [@jhosteny](https://www.ruby-forum.com/u/jhosteny)\
**Post date:** [October 9, 2006, 4:38pm UTC](https://www.ruby-forum.com/t/distributed-testing-with-test-unit-and-rinda/71514/1 "2006-10-09T16:38:38Z")

</div>

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 H.'s  
site at [http://segment7.net/projects/ruby/drb/rinda/ringserver.html](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 = name  
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 = Thread.current[:tuplespace][2]
  ts = Rinda::TupleSpaceProxy.new(ts) if consumer?
  ts.send(meth, *args)
end

```

end

class Producer \< Base  
def initialize(name)  
super  
ts = Rinda::TupleSpace.new  
name = “#{@name}:#{DRb.uri}”  
tuple = Rinda::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 = nil  
mattr\_accessor :service

```
@@file = nil
mattr_accessor :file

module Run
end

def start_client
  @@service = Service::Consumer.new('DistributedTest')
end
def start_server
  @@service = Service::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 = nil
    i = 0
    ObjectSpace.each_object do |obj|
      if (obj.class == Class and obj.to_s == name)
        klass = obj
        break
      end
      i += 1
    end
    log "Checked #{i} objects"
    begin
      test = klass.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 = File.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 = Thread.new do  
log “New thread”  
ClassMethods.service.consume do |srv|  
oid = method(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 = srv.take(tuple)  
args, method = tuple.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 = Test::Unit::Error  
klass = Test::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 = server  
@oid = oid  
end

```
  def method_missing(name, *args)
    name = name.id2name
    if name =~ /add_(.*)/
      if %W(failure error).include? $1
        args = args[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

---

<div class="post-metadata">

**Author:** ![jhosteny](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/bbe5ce/32.png) [@jhosteny](https://www.ruby-forum.com/u/jhosteny)\
**Post date:** [October 9, 2006, 9:34pm UTC](https://www.ruby-forum.com/t/distributed-testing-with-test-unit-and-rinda/71514/2 "2006-10-09T21:34:42Z")

</div>

## 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
