drunit 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,32 @@
1
+ # Distributed Ruby Unit (Testing)
2
+
3
+ A library for running tests across multiple applications from a single test case.
4
+
5
+
6
+ ## Basic usage:
7
+
8
+ class MainTest < Test::Unit::TestCase
9
+ include Drunit
10
+ RemoteApp(:fake_app, FAKE_APP_PATH + "/fake_app.rb")
11
+ RemoteApp(:rails_app, RAILS_APP_PATH + "/script/runner")
12
+
13
+ def test_should_use_the_same_db
14
+ id = in_app(:fake_app){
15
+ User.create!(:name => "Some Name")
16
+ }
17
+
18
+ in_app(:rails_app, id) do |id|
19
+ assert_nothing_raised() { User.find(id) }
20
+ end
21
+ end
22
+ end
23
+
24
+ ## Other "features"
25
+ * Automatically "Brings Up" and "Takes Down" your apps as needed. (Uses GC to tell when it's no longer needed.)
26
+ * Tracking of assertion counts.
27
+ * Beautified (a little) backtraces.
28
+ * Line number preserving between apps.
29
+ * Packed full of bugs (probably, I'm not sure).
30
+ * Packed full of 1.9 incompatibilities (I'm assuming).
31
+ * Packed full of space ninjas.
32
+ * Over 14% more awesome than a bag of chips.
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/gempackagetask'
6
+ require 'date'
7
+
8
+ desc 'Default: run unit tests.'
9
+ task :default => :test
10
+
11
+ desc 'run tests.'
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'lib'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = true
16
+ end
17
+
18
+ spec = Gem::Specification.new do |s|
19
+ s.name = %q{drunit}
20
+ s.version = "0.2"
21
+ s.summary = %q{A library for running tests across multiple applications from a single test case.}
22
+ s.description = %q{A library for running tests across multiple applications from a single test case.}
23
+
24
+ s.files = FileList['[A-Z]*', 'lib/**/*.rb', 'test/**/*.rb', 'rails/*']
25
+ s.executables = "drunit_remote"
26
+ s.require_path = 'lib'
27
+ s.test_files = Dir[*['test/**/*_test.rb']]
28
+
29
+ s.has_rdoc = true
30
+ s.extra_rdoc_files = ["README.markdown"]
31
+ s.rdoc_options = ['--line-numbers', '--inline-source', "--main", "README.markdown"]
32
+
33
+ s.authors = ["Tom Lea"]
34
+ s.email = %q{commits@tomlea.co.uk}
35
+
36
+ s.platform = Gem::Platform::RUBY
37
+ end
38
+
39
+ Rake::GemPackageTask.new spec do |pkg|
40
+ pkg.need_tar = true
41
+ pkg.need_zip = true
42
+ end
43
+
44
+ desc "Clean files generated by rake tasks"
45
+ task :clobber => [:clobber_rdoc, :clobber_package]
46
+
47
+ desc "Generate a gemspec file"
48
+ task :gemspec do
49
+ File.open("#{spec.name}.gemspec", 'w') do |f|
50
+ f.write spec.to_ruby
51
+ end
52
+ end
data/bin/drunit_remote ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), *%w[.. lib drunit remote_test])
4
+
5
+ include Drunit
6
+ require ARGV.first if ARGV.first
7
+
8
+ DRb.start_service nil, RemoteTest.new
9
+
10
+ STDOUT.puts "DRUNIT:URI #{DRb.uri}"
11
+ STDOUT.reopen(STDERR)
12
+
13
+ DRb.thread.join
@@ -0,0 +1,59 @@
1
+ require 'drb'
2
+ require 'ruby2ruby'
3
+
4
+ module Drunit
5
+ class RemoteApp
6
+ def initialize(name, boot = nil)
7
+ @name = name
8
+ @boot = boot
9
+ @boot ||= "#{name}/test/drunit_test_helper.rb" if File.exist? "#{name}/test/drunit_test_helper.rb"
10
+ @boot ||= "#{name}/test/test_helper.rb" if File.exist? "#{name}/test/test_helper.rb"
11
+ @remote_object = nil
12
+ end
13
+
14
+ def run(method_name, file, line, *args, &block)
15
+ raise_or_return(app.eval(block_to_source(method_name, &block), file, line, method_name, *args), method_name)
16
+ end
17
+
18
+ def last_assertion_count
19
+ app.last_assertion_count
20
+ end
21
+
22
+ private
23
+ def raise_or_return(e, method_name)
24
+ return e unless e.is_a? Exception
25
+ if first_remote_line = e.backtrace.grep(Regexp.new(method_name)).last
26
+ index = e.backtrace.index(first_remote_line)
27
+ raise e, e.message, e.backtrace[0..index] + ["RemoteApp<#{@name}>"] + caller(0)
28
+ end
29
+ raise e
30
+ end
31
+
32
+ def get_url(pipe)
33
+ pipe.each_line do |line|
34
+ return $1 if line =~ /^DRUNIT:URI (.*)$/
35
+ STDERR.puts "From drunit_remote>> #{line}"
36
+ end
37
+ end
38
+
39
+ def start_app!
40
+ drb_server = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "bin", "drunit_remote"))
41
+ pipe = IO.popen("#{drb_server} #{@boot}")
42
+ pid = pipe.pid
43
+ url = get_url(pipe) or raise "Could not establish connection to the remote drunit instance."
44
+ remote_object = DRbObject.new(nil, url)
45
+ ObjectSpace.define_finalizer(remote_object, proc{|id| Process.kill("KILL", pid) && Process.wait})
46
+ return remote_object
47
+ end
48
+
49
+ def app
50
+ @remote_object ||= start_app!
51
+ end
52
+
53
+ def block_to_source(method_name, &block)
54
+ m = Module.new
55
+ m.send(:define_method, method_name, &block)
56
+ Ruby2Ruby.translate(m, method_name)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,54 @@
1
+ require 'drb'
2
+ require 'test/unit'
3
+
4
+ module Drunit
5
+ class RemoteError < RuntimeError
6
+ def initialize(real_exception)
7
+ @real_exception = real_exception
8
+ end
9
+ end
10
+
11
+ class RemoteTest
12
+ attr_reader :last_assertion_count
13
+
14
+ class TestCase
15
+ include Test::Unit::Assertions
16
+ attr_reader :assertion_count
17
+
18
+ def initialize(code, source_file, source_line, method_name)
19
+ @assertion_count = 0
20
+ @method_name = method_name
21
+ instance_eval(code, source_file, source_line)
22
+ end
23
+
24
+ def add_assertion
25
+ @assertion_count += 1
26
+ end
27
+
28
+ def run(*args)
29
+ send(@method_name, *args)
30
+ end
31
+ end
32
+
33
+ def eval(code, source_file, source_line, method_name, *args)
34
+ test_case = TestCase.new(code, source_file, source_line, method_name)
35
+ return rewrite_exceptions{ test_case.run(*args) }
36
+ rescue Exception => e
37
+ return e
38
+ ensure
39
+ @last_assertion_count = defined?(test_case.assertion_count) ? test_case.assertion_count : 0
40
+ end
41
+ private
42
+ # We need to strib down and generalise the exceptions to prevent the integration project from having to know anything about anything.
43
+ def rewrite_exceptions
44
+ return yield
45
+ rescue Test::Unit::AssertionFailedError
46
+ raise
47
+ rescue ArgumentError => e
48
+ raise e unless e.message =~ /Anonymous modules /
49
+ raise NameError, "Const Missing", e.backtrace[3..-1]
50
+ rescue Exception => e
51
+ raise Drunit::RemoteError.new(e.class.name), e.message, e.backtrace
52
+ end
53
+ end
54
+ end
data/lib/drunit.rb ADDED
@@ -0,0 +1,47 @@
1
+ module Drunit
2
+ class RemoteError < RuntimeError
3
+ def self.name
4
+ @@name
5
+ end
6
+
7
+ def class
8
+ if type = look_up_exception
9
+ return type
10
+ else
11
+ @@name = @real_exception.to_s
12
+ super
13
+ end
14
+ end
15
+
16
+ def look_up_exception
17
+ @real_exception.split("::").inject(Object){|node, part|
18
+ node && node.const_defined?(part) && node.const_get(part)
19
+ }
20
+ end
21
+ end
22
+
23
+ module ClassMethods
24
+ def RemoteApp(name, *args)
25
+ const_set "RemoteAppFor_#{name}", RemoteApp.new(name, *args)
26
+ end
27
+ end
28
+
29
+ def in_app(name, *args, &block)
30
+ file, line, method = caller(2).first.split(":")
31
+ method ||= "unknown_method"
32
+ remote_app_for(name).run(method.gsub(/^in /, "").gsub(/[^a-zA-Z0-9_?!]/, ""), file, line.to_i, *args, &block)
33
+ ensure
34
+ remote_app_for(name).last_assertion_count.times{ add_assertion } rescue nil
35
+ end
36
+
37
+ def remote_app_for(name)
38
+ self.class.const_get("RemoteAppFor_#{name}")
39
+ end
40
+
41
+ def self.included(other)
42
+ other.send(:extend, ClassMethods)
43
+ end
44
+ end
45
+
46
+ require File.join(File.dirname(__FILE__), *%w[drunit remote_app])
47
+
@@ -0,0 +1,13 @@
1
+ class SomeFoo
2
+ def multiply(a,b)
3
+ a * b
4
+ end
5
+ end
6
+
7
+ class SomeException < Exception
8
+ end
9
+
10
+ module MyModule
11
+ class SomeOtherException < Exception
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
2
+ require "test/unit"
3
+ require "drunit"
4
+
5
+ FAKE_APP_PATH = File.join(File.dirname(__FILE__), *%w[fake_app])
@@ -0,0 +1,13 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. test_helper])
2
+
3
+ class ExceptionHandlingTest < Test::Unit::TestCase
4
+ include Drunit
5
+ RemoteApp(:fake_app, FAKE_APP_PATH + "/fake_app.rb")
6
+ def InApp(*args, &block)
7
+ in_app(:fake_app, *args, &block)
8
+ end
9
+
10
+ def test_should_raise_a_generic_exception
11
+ assert_raise(Drunit::RemoteError) { InApp{ raise MyModule::SomeOtherException} }
12
+ end
13
+ end
@@ -0,0 +1,41 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. test_helper])
2
+
3
+ class MainTest < Test::Unit::TestCase
4
+ include Drunit
5
+ RemoteApp(:fake_app, FAKE_APP_PATH + "/fake_app.rb")
6
+ def InApp(*args, &block)
7
+ in_app(:fake_app, *args, &block)
8
+ end
9
+
10
+ def test_should_not_raise_in_a_basic_case
11
+ InApp do
12
+ assert true
13
+ end
14
+ end
15
+
16
+ def test_should_raise_an_exception
17
+ assert_raise(RuntimeError) { InApp{ raise "Fail" } }
18
+ end
19
+
20
+ def test_should_raise_an_exception_faking_its_class_name_if_we_have_never_heard_of_it
21
+ InApp{ raise SomeException, "Fooo" }
22
+ flunk "Should have raised."
23
+ rescue => e
24
+ assert_equal "SomeException", e.class.name
25
+ end
26
+
27
+ def test_should_inject_the_fact_we_are_in_a_remote_app_into_the_backtrace
28
+ e = assert_raise(RuntimeError) { InApp{ raise "Fail" } }
29
+ assert_equal 1, e.backtrace.grep("RemoteApp<fake_app>").size
30
+ end
31
+
32
+ def test_should_raise_the_assertion_count_when_we_assert
33
+ original_count = @_result.assertion_count
34
+ InApp{ assert true}
35
+ assert_equal original_count + 1, @_result.assertion_count
36
+ end
37
+
38
+ def test_should_be_able_to_pass_in_simple_params
39
+ assert_equal 12, InApp(2,6){|a,b| SomeFoo.new.multiply(a, b)}
40
+ end
41
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: drunit
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.2"
5
+ platform: ruby
6
+ authors:
7
+ - Tom Lea
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-07 00:00:00 +01:00
13
+ default_executable: drunit_remote
14
+ dependencies: []
15
+
16
+ description: A library for running tests across multiple applications from a single test case.
17
+ email: commits@tomlea.co.uk
18
+ executables:
19
+ - drunit_remote
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.markdown
24
+ files:
25
+ - Rakefile
26
+ - README.markdown
27
+ - lib/drunit/remote_app.rb
28
+ - lib/drunit/remote_test.rb
29
+ - lib/drunit.rb
30
+ - test/fake_app/fake_app.rb
31
+ - test/test_helper.rb
32
+ - test/unit/exception_handling_test.rb
33
+ - test/unit/main_test.rb
34
+ - bin/drunit_remote
35
+ has_rdoc: true
36
+ homepage:
37
+ post_install_message:
38
+ rdoc_options:
39
+ - --line-numbers
40
+ - --inline-source
41
+ - --main
42
+ - README.markdown
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ requirements: []
58
+
59
+ rubyforge_project:
60
+ rubygems_version: 1.3.1
61
+ signing_key:
62
+ specification_version: 2
63
+ summary: A library for running tests across multiple applications from a single test case.
64
+ test_files:
65
+ - test/unit/exception_handling_test.rb
66
+ - test/unit/main_test.rb