gmalamid-synthesis 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
data/COPYING ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2007 nutrun.com, Stuart Caborn, George Malamidis
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,107 @@
1
+ = Synthesis
2
+
3
+ == Philosophy
4
+
5
+ Currently we believe that developers are writing unnecessary <em>dependency wired</em> tests to cover uncertainty about the validity of simulated interactions in their <em>dependency neutral</em> tests. In other words, we cannot be certain that all our simulated interaction based tests 'join up'. If it were possible to correlate the simulated interactions in our tests, then we should be able to do away with the need to write large numbers of complex, slow and brittle wired tests (apart from those which interact with the boundaries of the SUT).
6
+
7
+ Synthesis combines lightweight tests to build confidence that the system under test is complete and reduces the need for large, overarching tests.
8
+
9
+ == Installation
10
+
11
+ sudo gem i synthesis
12
+
13
+ == Download
14
+
15
+ Synthesis RubyForge page ( http://rubyforge.org/projects/synthesis )
16
+
17
+ == Dependencies
18
+
19
+ Synthesis's core doesn't have any dependencies.
20
+
21
+ When used with the Mocha adapter, it will depend on the Mocha[http://mocha.rubyforge.org] library.
22
+
23
+ When used with the RSpec adapter, it will depend on the RSpec[http://rspec.info/] library.
24
+
25
+ When used with the Expectations adapter, it will depend on the Expectations[http://expectations.rubyforge.org] library.
26
+
27
+ == Usage
28
+
29
+ Synthesis can be used through its Rake task. It currently has three supported adapters: Mocha (with Test::Unit), RSpec and Expectations. If +adapter+ is not explicitly specified, the Mocha adapter will be used by default.
30
+
31
+ By default, Synthesis outputs to +STDOUT+, but output can be redirected to alternative IO streams.
32
+
33
+ Synthesis can be setup to ignore certain classes or modules when collecting expectations for verification.
34
+
35
+ If +pattern+ is not specified, it will default to <tt>test/**/*_test.rb</tt>
36
+
37
+ == Usage examples
38
+
39
+ To use with Test::Unit and Mocha, ignoring Array and Hash:
40
+
41
+ require "synthesis/task"
42
+
43
+ Synthesis::Task.new do |t|
44
+ t.pattern = 'test/unit/**/*_test.rb'
45
+ t.ignored = [Array, Hash]
46
+ end
47
+
48
+ To use with RSpec, running all specs in the <tt>spec</tt> directory:
49
+
50
+ require "synthesis/task"
51
+
52
+ Synthesis::Task.new do |t|
53
+ t.adapter = :rspec
54
+ t.pattern = 'spec/**/*_spec.rb'
55
+ end
56
+
57
+ To use with Expectations, redirecting output to a file
58
+
59
+ require "synthesis/task"
60
+
61
+ Synthesis::Task.new do |t|
62
+ t.adapter = :expectations
63
+ t.out = File.new "synthesis.test.txt", "a"
64
+ end
65
+
66
+ == Utilities
67
+
68
+ === mock_instance
69
+
70
+ require "synthesis/util/mock_instance"
71
+ foo_mock = Foo.mock_instance(arg_one, arg_2)
72
+
73
+ This is equivalent, but without calling the real <tt>initialize</tt>, to:
74
+
75
+ foo_mock = Foo.new
76
+ Foo.expects(:new).with(arg_one, arg_two).returns(foo_mock)
77
+
78
+ Or, in the case of RSpec, it is equivalent to:
79
+
80
+ foo_mock = Foo.new
81
+ Foo.should_receive(:new).with(arg_one, arg_two).and_return(foo_mock)
82
+
83
+ Either <tt>"mocha_standalone"</tt> or <tt>"spec/mocks"</tt> need to be required before using <tt>mock_instance</tt>.
84
+
85
+ == Git
86
+
87
+ Public clone URL: git://github.com/gmalamid/synthesis.git
88
+
89
+ == Known Issues
90
+
91
+ Reporting the location (filename and line number) of tested/untested expectations doesn't work as intended with the Expectations adapter.
92
+
93
+ == Contributors
94
+
95
+ Danilo Sato, Paul Nasrat
96
+
97
+ == Discuss
98
+
99
+ http://groups.google.com/group/synthesized-testing
100
+
101
+ == Related reading
102
+
103
+ * Synthesized Testing: A Primer ( http://nutrun.com/weblog/synthesized-testing-a-primer )
104
+ * Confidence as a test code metric ( http://nutrun.com/weblog/confidence-as-a-test-code-metric )
105
+ * Using Synthesis With Test::Unit and Mocha ( http://nutrun.com/weblog/using-synthesis-with-testunit-and-mocha )
106
+ * Using Synthesis With Expectations ( http://nutrun.com/weblog/using-synthesis-with-expectations )
107
+ * Synthesis validates simulated method calls taking method signature arguments into account ( http://nutrun.com/weblog/synthesis-002 )
data/Rakefile ADDED
@@ -0,0 +1,77 @@
1
+ require "rubygems"
2
+ require "rake/testtask"
3
+ require "rake/gempackagetask"
4
+ require 'rake/rdoctask'
5
+ require 'rake/contrib/sshpublisher'
6
+ require File.dirname(__FILE__) + "/lib/synthesis/task"
7
+
8
+ load 'synthesis.gemspec'
9
+
10
+ task :default => :test
11
+
12
+ desc "Run all tests"
13
+ task :test => %w[test:core test:mocha test:spec]
14
+
15
+ desc "Run core tests"
16
+ Rake::TestTask.new('test:core') do |t|
17
+ t.pattern = 'test/synthesis/*_test.rb'
18
+ end
19
+
20
+ desc "Run Mocha adapter tests"
21
+ Rake::TestTask.new('test:mocha') do |t|
22
+ t.pattern = 'test/synthesis/adapter/mocha/*_test.rb'
23
+ end
24
+
25
+ desc "Run RSpec adapter tests"
26
+ Rake::TestTask.new('test:spec') do |t|
27
+ t.pattern = 'test/synthesis/adapter/rspec/*_test.rb'
28
+ end
29
+
30
+ Synthesis::Task.new do |t|
31
+ t.pattern = 'test_project/mocha/test/*_test.rb'
32
+ t.ignored = [Array, Hash]
33
+ # t.out = File.new('synthesis.test.txt', 'a')
34
+ end
35
+
36
+ Synthesis::Task.new('synthesis:expectations') do |t|
37
+ t.adapter = :expectations
38
+ t.pattern = 'test_project/expectations/test/*_test.rb'
39
+ end
40
+
41
+ Synthesis::Task.new('synthesis:spec') do |t|
42
+ t.adapter = :rspec
43
+ t.pattern = 'test_project/rspec/*_spec.rb'
44
+ end
45
+
46
+ desc 'Generate RDoc'
47
+ Rake::RDocTask.new do |task|
48
+ task.main = 'README'
49
+ task.title = 'synthesis'
50
+ task.rdoc_dir = 'doc'
51
+ task.options << "--line-numbers" << "--inline-source"
52
+ task.rdoc_files.include('README', 'lib/**/*.rb')
53
+ end
54
+
55
+ desc "Upload RDoc to RubyForge"
56
+ task :publish_rdoc do
57
+ Rake::Task[:rdoc].invoke
58
+ Rake::SshDirPublisher.new("gmalamid@rubyforge.org", "/var/www/gforge-projects/synthesis", "doc").upload
59
+ end
60
+
61
+ Rake::GemPackageTask.new(GEMSPEC) do |t|
62
+ t.need_zip = false
63
+ t.need_tar = false
64
+ end
65
+
66
+ desc "Remove rdoc and package artefacts"
67
+ task :clean => %w[clobber_package clobber_rdoc]
68
+
69
+ task(:lf) {p Dir["lib/**/*rb"] }
70
+
71
+ task(:check_gemspec) do
72
+ require 'rubygems/specification'
73
+ data = File.read('synthesis.gemspec')
74
+ spec = nil
75
+ Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join
76
+ puts spec
77
+ end
@@ -0,0 +1,35 @@
1
+ require "rubygems"
2
+ require "mocha_standalone"
3
+ require "expectations"
4
+
5
+ require File.dirname(__FILE__) + "/../../synthesis"
6
+
7
+ module Synthesis
8
+ class ExpectationsAdapter < Adapter
9
+ def run
10
+ fail_unless { Expectations::SuiteRunner.instance.suite.execute }
11
+ end
12
+
13
+ def collect_expectations
14
+ ignore_instances_of Class::AnyInstance
15
+ Object.extend(ExpectationRecordEnabled)
16
+ Object.record_expectations_on(:expects)
17
+ Mocha::Expectation.extend(ExpectationInterceptor)
18
+ Mocha::Expectation.record_expected_arguments_on(:with)
19
+ Mocha::Expectation.record_expected_return_values_on(:returns)
20
+ Mocha::Expectation.record_expected_return_values_on(:raises)
21
+ Mocha::Expectation.remove_expectation_on(:never)
22
+ end
23
+
24
+ def stop_collecting_expectations
25
+ Mocha::Expectation.stop_intercepting!
26
+ Object.stop_recording!
27
+ end
28
+ end
29
+ end
30
+
31
+ class Expectations::SuiteRunner
32
+ def initialize
33
+ self.suite = Expectations::Suite.new
34
+ end
35
+ end
@@ -0,0 +1,30 @@
1
+ require "rubygems"
2
+ require "mocha_standalone"
3
+ require "test/unit"
4
+
5
+ require File.dirname(__FILE__) + "/../../synthesis"
6
+
7
+ module Synthesis
8
+ class MochaAdapter < Adapter
9
+ def run
10
+ Test::Unit.run = true # Yes means no...
11
+ fail_unless { Test::Unit::AutoRunner.run }
12
+ end
13
+
14
+ def collect_expectations
15
+ ignore_instances_of Class::AnyInstance
16
+ Object.extend(ExpectationRecordEnabled)
17
+ Object.record_expectations_on(:expects)
18
+ Mocha::Expectation.extend(ExpectationInterceptor)
19
+ Mocha::Expectation.record_expected_arguments_on(:with)
20
+ Mocha::Expectation.record_expected_return_values_on(:returns)
21
+ Mocha::Expectation.record_expected_return_values_on(:raises)
22
+ Mocha::Expectation.remove_expectation_on(:never)
23
+ end
24
+
25
+ def stop_collecting_expectations
26
+ Mocha::Expectation.stop_intercepting!
27
+ Object.stop_recording!
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,34 @@
1
+ require "rubygems"
2
+ require "spec"
3
+ require "spec/mocks"
4
+
5
+ require File.dirname(__FILE__) + "/../../synthesis"
6
+
7
+ module Synthesis
8
+ class RSpecAdapter < Adapter
9
+ def run
10
+ rspec_options.files.clear
11
+ fail_unless do
12
+ rspec_options.instance_variable_set(:@formatters, nil)
13
+ # rspec_options.instance_variable_set(:@format_options, [["profile", STDOUT]])
14
+ rspec_options.run_examples
15
+ end
16
+ end
17
+
18
+ def collect_expectations
19
+ ignore_instances_of Spec::Mocks::Mock
20
+ Spec::Mocks::Methods.extend(ExpectationRecordEnabled)
21
+ Spec::Mocks::Methods.record_expectations_on(:should_receive)
22
+ Spec::Mocks::MessageExpectation.extend(ExpectationInterceptor)
23
+ Spec::Mocks::MessageExpectation.record_expected_arguments_on(:with)
24
+ Spec::Mocks::MessageExpectation.record_expected_return_values_on(:and_return)
25
+ Spec::Mocks::MessageExpectation.record_expected_return_values_on(:and_raise)
26
+ Spec::Mocks::MessageExpectation.remove_expectation_on(:never)
27
+ end
28
+
29
+ def stop_collecting_expectations
30
+ Spec::Mocks::MessageExpectation.stop_intercepting!
31
+ Spec::Mocks::Methods.stop_recording!
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,42 @@
1
+ module Synthesis
2
+ # Subclasses of Adapter must implement the run, collect_expectations and
3
+ # stop_collecting_expectations methods.
4
+ # For example implementations refer to Synthesis::MochaAdapter and
5
+ # Synthesis::RSpecAdapter.
6
+ class Adapter
7
+ include Logging
8
+
9
+ def initialize(pattern)
10
+ @pattern = pattern
11
+ end
12
+
13
+ # The block parameter will yield twice, once for collecting and
14
+ # once for verifying the collected expectations.
15
+ def fail_unless(&block)
16
+ log "Collecting expectations..."
17
+ collect_expectations
18
+ Dir[*@pattern].each { |t| require t }
19
+ exit -1 unless yield
20
+ log "Verifying expectation invocations..."
21
+ stop_collecting_expectations
22
+ ExpectationRecord.record_invocations
23
+ yield
24
+ end
25
+
26
+ # The type of object representing a mock or stub for the test framework.
27
+ # Objects of this type will be ignored by Synthesis.
28
+ def ignore_instances_of(type)
29
+ Synthesis.const_set(:MOCK_OBJECT, type)
30
+ end
31
+
32
+ class << self
33
+ def inherited(subclass)
34
+ @adapter = subclass
35
+ end
36
+
37
+ def load(pattern)
38
+ @adapter.new(pattern)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,5 @@
1
+ class Class
2
+ def expectation(method, track, args = [], return_value = nil)
3
+ Synthesis::Expectation::Singleton.new(self, method, track, args, return_value)
4
+ end
5
+ end
@@ -0,0 +1,115 @@
1
+ module Synthesis
2
+ module Expectation
3
+ def self.new(receiver, method, track, args = [], return_values = [])
4
+ receiver.expectation(method, track, args, return_values)
5
+ end
6
+
7
+ class Expectation
8
+ include Logging
9
+ attr_reader :receiver, :method, :return_values
10
+ protected :return_values
11
+ attr_accessor :args
12
+
13
+ def initialize(receiver, method, track, args, return_values)
14
+ @receiver, @method, @track, @args = receiver, method, track, args
15
+ @return_values = return_values
16
+ end
17
+
18
+ def record_invocations
19
+ meta_receiver.extend(Recordable)
20
+ meta_receiver.recordable_method(@method)
21
+ end
22
+
23
+ def explode
24
+ if @return_values.size > 1
25
+ @return_values.map do |v|
26
+ expectation = self.class.new(@receiver, @method, @track, @args, [])
27
+ expectation.add_return_values(v)
28
+ expectation
29
+ end
30
+ else
31
+ self
32
+ end
33
+ end
34
+
35
+ def eql?(other)
36
+ ExpectationMatcher.new(self, other).match?
37
+ end
38
+
39
+ def ==(other)
40
+ eql?(other)
41
+ end
42
+
43
+ def invoked?
44
+ @invoked
45
+ end
46
+
47
+ def invoked!
48
+ @invoked = true
49
+ end
50
+
51
+ def arg_types
52
+ args.map { |arg| arg.class }
53
+ end
54
+
55
+ def return_value_type
56
+ @return_values.size == 1 ? @return_values[0].class : nil
57
+ end
58
+
59
+ def add_return_values(*vals)
60
+ @return_values_defined = true
61
+ @return_values += vals
62
+ end
63
+
64
+ def return_values_defined?
65
+ @return_values_defined
66
+ end
67
+ end
68
+
69
+ class Singleton < Expectation
70
+ def meta_receiver
71
+ @receiver.__metaclass__
72
+ end
73
+
74
+ def hash
75
+ (@receiver.name.hash * 23) + @method.hash
76
+ end
77
+
78
+ def receiver_class
79
+ @receiver
80
+ end
81
+
82
+ def to_s
83
+ "(#{return_value_type}) " +
84
+ "#{@receiver.name}.#{@method}(#{@args.map { |arg| arg.class } * ', '})" +
85
+ "in #{@track}"
86
+ end
87
+ end
88
+
89
+ class Instance < Expectation
90
+ def meta_receiver
91
+ @receiver.class
92
+ end
93
+
94
+ def hash
95
+ (meta_receiver.name.hash * 23) + @method.hash
96
+ end
97
+
98
+ def receiver_class
99
+ meta_receiver
100
+ end
101
+
102
+ def to_s
103
+ "(#{return_value_type}) #{meta_receiver.name}.new.#{@method}" +
104
+ "(#{@args.map { |arg| arg.class } * ', '})" +
105
+ "in #{@track}"
106
+ end
107
+ end
108
+
109
+ class NilExpectation < Expectation
110
+ def initialize;end
111
+ def invoked!;end
112
+ def record_invocations;end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,78 @@
1
+ module Synthesis
2
+ # Extend by the mock object framework's expectation mechanism to allow
3
+ # Synthesis to tap into it in order to collect simulated method arguments
4
+ # and return values.
5
+ module ExpectationInterceptor
6
+ # Intercept the mock object framework's expectation method for declaring a mocked
7
+ # method's arguments so that Synthesis can record them.
8
+ def record_expected_arguments_on(method_name)
9
+ (@original_methods ||= []) << method_name
10
+
11
+ class_eval do
12
+ alias_method "intercepted_#{method_name}", method_name
13
+
14
+ define_method(:get_method_name) {method_name}
15
+
16
+ def temp_method(*expected_parameters, &matching_block)
17
+ synthesis_expectation.args = expected_parameters if synthesis_expectation
18
+ send("intercepted_#{get_method_name}", *expected_parameters, &matching_block)
19
+ end
20
+
21
+ alias_method method_name, :temp_method
22
+ undef temp_method
23
+ end
24
+ end
25
+
26
+ # Intercept the mock object framework's expectation method for declaring a mocked
27
+ # method's return values so that Synthesis can record them.
28
+ def record_expected_return_values_on(method_name)
29
+ (@original_methods ||= []) << method_name
30
+
31
+ class_eval do
32
+ alias_method "intercepted_#{method_name}", method_name
33
+
34
+ define_method(method_name) do |*values|
35
+ mock_expectation = send("intercepted_#{method_name}", *values)
36
+ synthesis_expectation.add_return_values(*values) if synthesis_expectation
37
+ mock_expectation
38
+ end
39
+ end
40
+ end
41
+
42
+ def remove_expectation_on(method_name)
43
+ (@original_methods ||= []) << method_name
44
+
45
+ class_eval do
46
+ alias_method "intercepted_#{method_name}", method_name
47
+
48
+ define_method(method_name) do |*values|
49
+ Synthesis::ExpectationRecord.remove(synthesis_expectation) if synthesis_expectation
50
+ send("intercepted_#{method_name}")
51
+ end
52
+ end
53
+ end
54
+
55
+ # Restore the original methods ExpectationInterceptor has rewritten and
56
+ # undefine their intercepted counterparts. Undefine the synthesis_expectation
57
+ # accessors.
58
+ def stop_intercepting!
59
+ @original_methods.each do |m|
60
+ class_eval do
61
+ alias_method m, "intercepted_#{m}"
62
+ remove_method "intercepted_#{m}"
63
+ end
64
+ end
65
+
66
+ class_eval do
67
+ remove_method :synthesis_expectation
68
+ remove_method :synthesis_expectation=
69
+ end
70
+ end
71
+
72
+ # Classes extending ExpectationInterceptor will have a synthesis_expectation
73
+ # attribute accessor added to them.
74
+ def self.extended(receiver)
75
+ receiver.send(:attr_accessor, :synthesis_expectation)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,23 @@
1
+ module Synthesis
2
+ class ExpectationMatcher
3
+ def initialize(expectation_one, expectation_two)
4
+ @expectation_one, @expectation_two = expectation_one, expectation_two
5
+ end
6
+
7
+ def match?
8
+ return false unless @expectation_one.class == @expectation_two.class
9
+ return false unless @expectation_one.receiver_class == @expectation_two.receiver_class
10
+ return false unless @expectation_one.method == @expectation_two.method
11
+ return false unless @expectation_one.arg_types == @expectation_two.arg_types
12
+ return false unless return_values_match?
13
+ true
14
+ end
15
+
16
+ private
17
+
18
+ def return_values_match?
19
+ return true unless @expectation_one.return_values_defined? || @expectation_two.return_values_defined?
20
+ @expectation_one.return_value_type == @expectation_two.return_value_type
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,102 @@
1
+ module Synthesis
2
+ class ExpectationRecord
3
+ class << self
4
+ include Logging
5
+
6
+ def add_expectation(receiver, method, track, args = [])
7
+ unless ignore?(receiver)
8
+ expectation = Expectation.new(receiver, method, track, args)
9
+ expectations << expectation
10
+ expectation
11
+ end
12
+ end
13
+
14
+ def remove(expectation)
15
+ expectations.delete(expectation)
16
+ end
17
+
18
+ def ignore(*args)
19
+ ignored.merge(args)
20
+ end
21
+
22
+ def expectations
23
+ # Using an Array instead of a Set because the +Expectation+ instance
24
+ # is not complete when first added. A Set would result to possible duplicates.
25
+ # obj.expects(:method).with(:args)
26
+ # the +Expectation+ will be added when obj.expects(:method) is called
27
+ # the +Expectation+ arguments will be added when .with(:args) is called
28
+ @expectations ||= []
29
+ end
30
+
31
+ def ignored
32
+ @ignored ||= Set.new
33
+ end
34
+
35
+ def [](matcher)
36
+ # Using a hash for faster look up of expectations
37
+ # when recording invocations
38
+ expectations_with_return_values[matcher] ||
39
+ expectations_without_return_values[matcher] ||
40
+ Expectation::NilExpectation.new
41
+ end
42
+
43
+ def record_invocations
44
+ expectations.map! { |e| e.explode }
45
+ expectations.flatten!
46
+ expectations.uniq!
47
+ expectations.each do |e|
48
+ e.record_invocations
49
+ if e.return_values_defined?
50
+ expectations_with_return_values[e] = e
51
+ else
52
+ expectations_without_return_values[e] = e
53
+ end
54
+ end
55
+ end
56
+
57
+ def tested_expectations
58
+ expectations.select { |e| e.invoked? }
59
+ end
60
+
61
+ def untested_expectations
62
+ expectations.select { |e| !e.invoked? }
63
+ end
64
+
65
+ def print_tested_expectations
66
+ log; log "Tested Expectations: "
67
+ tested_expectations.each { |e| log e }
68
+ end
69
+
70
+ def print_untested_expectations
71
+ log; log "Untested Expectations: "
72
+ untested_expectations.each { |e| log e }
73
+ end
74
+
75
+ def print_ignored
76
+ log; log "Ignoring: #{ignored.to_a * ', '}"
77
+ end
78
+
79
+ private
80
+ def expectations_with_return_values
81
+ @expectations_with_return_values ||= {}
82
+ end
83
+
84
+ def expectations_without_return_values
85
+ @expectations_without_return_values ||= {}
86
+ end
87
+
88
+ def ignore?(obj)
89
+ ignored.include?(obj.class) ||
90
+ (obj.is_a?(Class) && ignored.include?(obj)) ||
91
+ (obj.is_a?(Module) && ignored.include?(obj)) ||
92
+ obj.is_a?(MOCK_OBJECT)
93
+ end
94
+
95
+ def reset!
96
+ @expectations_with_return_values = nil
97
+ @expectations_without_return_values = nil
98
+ @expectations, @ignored = nil
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,39 @@
1
+ module Synthesis
2
+ # Extend by the mock object framework's construct for declaring a
3
+ # mock object so that Synthesis can tap into it in order to record
4
+ # the expectation.
5
+ module ExpectationRecordEnabled
6
+ # Intercept the mock object framework's method for declaring a mock
7
+ # object so that Synthesis can record it.
8
+ def record_expectations_on(method_name)
9
+ @original_expects = method_name
10
+
11
+ class_eval do
12
+ alias_method "intercepted_#{method_name}", method_name
13
+
14
+ define_method(:get_expectation_method_name) {method_name}
15
+
16
+ def temp_expectation_record(meth, *expected_parameters, &matching_block)
17
+ s_expectation = ExpectationRecord.add_expectation(self, meth, caller[0])
18
+ m_expectation = send("intercepted_#{get_expectation_method_name}", meth, *expected_parameters, &matching_block)
19
+ m_expectation.synthesis_expectation = s_expectation
20
+ m_expectation
21
+ end
22
+
23
+ alias_method method_name, :temp_expectation_record
24
+ undef temp_expectation_record
25
+ end
26
+ end
27
+
28
+ # Restore the original methods ExpectationRecordEnabled has rewritten and
29
+ # undefine their intercepted counterparts.
30
+ def stop_recording!
31
+ method_name = @original_expects
32
+ class_eval do
33
+ alias_method method_name, "intercepted_#{method_name}"
34
+ remove_method "intercepted_#{method_name}"
35
+ remove_method :get_expectation_method_name
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,21 @@
1
+ module Synthesis
2
+ module Logging
3
+ def silence!
4
+ @silent = true
5
+ end
6
+
7
+ def speak!
8
+ @silent = false
9
+ end
10
+
11
+ protected
12
+
13
+ def log(msg = '')
14
+ out.puts "[Synthesis] #{msg}" unless @silent
15
+ end
16
+
17
+ def out
18
+ Synthesis::Logging.const_defined?(:OUT) ? OUT : STDOUT
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,8 @@
1
+ module Synthesis
2
+ class MethodInvocationWatcher
3
+ def self.invoked(receiver, method, args = [], return_values = [])
4
+ matcher = Expectation.new(receiver, method, nil, args, return_values)
5
+ ExpectationRecord[matcher].invoked!
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ class Module
2
+ def expectation(method, track, args = [], return_value = nil)
3
+ Synthesis::Expectation::Singleton.new(self, method, track, args, return_value)
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ class Object
2
+ def __metaclass__
3
+ class << self; self end
4
+ end
5
+
6
+ def expectation(method, track, args = [], return_value = nil)
7
+ Synthesis::Expectation::Instance.new(self, method, track, args, return_value)
8
+ end
9
+ end
@@ -0,0 +1,48 @@
1
+ module Synthesis
2
+ module Recordable
3
+ def recordable_method(meth)
4
+ if method_defined?(meth)
5
+ defined_recordable_method(meth)
6
+ else
7
+ magic_recordable_method(meth)
8
+ end
9
+ end
10
+
11
+ protected
12
+
13
+ def defined_recordable_method(meth)
14
+ unless method_defined?("__recordable__#{meth}".intern)
15
+ alias_method "__recordable__#{meth}".intern, meth
16
+ class_eval <<-end_eval
17
+ def #{meth}(*args, &block)
18
+ begin
19
+ return_value = send("__recordable__#{meth}", *args, &block)
20
+ MethodInvocationWatcher.invoked(self, "#{meth}".intern, args, [return_value])
21
+ return_value
22
+ rescue Exception => e
23
+ MethodInvocationWatcher.invoked(self, "#{meth}".intern, args, [e])
24
+ raise e
25
+ end
26
+ end
27
+ end_eval
28
+ end
29
+ end
30
+
31
+ def magic_recordable_method(meth)
32
+ class_eval <<-end_eval
33
+ def #{meth}(*args)
34
+ begin
35
+ return_value = method_missing(:#{meth}, *args)
36
+ MethodInvocationWatcher.invoked(self, "#{meth}".intern, args, [return_value])
37
+ return_value
38
+ rescue Exception => e
39
+ MethodInvocationWatcher.invoked(self, "#{meth}".intern, args, [e])
40
+ raise e
41
+ end
42
+ end
43
+
44
+ def __recordable__#{meth}() raise "Don't ever call me" end
45
+ end_eval
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,24 @@
1
+ module Synthesis
2
+ class Reporter
3
+ class << self
4
+ include Logging
5
+
6
+ def report
7
+ if failed?
8
+ ExpectationRecord.print_tested_expectations
9
+ ExpectationRecord.print_untested_expectations
10
+ ExpectationRecord.print_ignored
11
+ log; log "FAILED."
12
+ return -1
13
+ end
14
+ log; log "Verified #{ExpectationRecord.expectations.size} expectations"
15
+ log "SUCCESS."
16
+ 0
17
+ end
18
+
19
+ def failed?
20
+ ExpectationRecord.untested_expectations.any?
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,9 @@
1
+ module Synthesis
2
+ class Runner
3
+ def self.run(adapter, pattern)
4
+ require "synthesis/adapter/#{adapter}"
5
+ Adapter.load(pattern).run
6
+ at_exit { Reporter.report unless $! }
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ require "rake"
3
+ require "rake/tasklib"
4
+ require File.dirname(__FILE__) + "/../synthesis/logging"
5
+
6
+ module Synthesis
7
+ class Task < Rake::TaskLib
8
+ include Logging
9
+ attr_accessor :verbose, :pattern, :ruby_opts, :adapter, :out, :ignored, :libs
10
+
11
+ def initialize(name='synthesis:test')
12
+ @name, @ignored, @libs = name, [], ['lib']
13
+ yield self if block_given?
14
+ @pattern ||= 'test/**/*_test.rb'
15
+ @ruby_opts ||= []
16
+ @adapter ||= :mocha
17
+ define
18
+ end
19
+
20
+ def ignore(*classes)
21
+ STDERR.puts
22
+ STDERR.puts "DEPRECATION WARNING!!!"
23
+ STDERR.puts caller[0]
24
+ STDERR.puts "Synthesis::Task#ignore(*classes) has been deprecated."
25
+ STDERR.puts "Use Synthesis::Task#ignored = [#{classes * ','}] instead."
26
+ STDERR.puts
27
+ @ignored << classes
28
+ @ignored.flatten!
29
+ end
30
+
31
+ def define
32
+ desc "Run Synthesis tests"
33
+ task @name do
34
+ RakeFileUtils.verbose(@verbose) do
35
+ load_paths
36
+ require File.dirname(__FILE__) + "/../synthesis"
37
+ require File.dirname(__FILE__) + "/../synthesis/runner"
38
+ Synthesis::Logging.const_set(:OUT, @out) if @out
39
+ Synthesis::ExpectationRecord.ignore(*@ignored)
40
+ Synthesis::Runner.run(@adapter, @pattern)
41
+ end
42
+ end
43
+ self
44
+ end
45
+
46
+ private
47
+ def load_paths
48
+ @libs.each { |path| $:.unshift(File.join(Dir.pwd, path)) }
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,18 @@
1
+ class Object
2
+ def self.mock_instance(*args)
3
+ class_eval do
4
+ alias original_initialize initialize
5
+ def initialize()end
6
+ end
7
+
8
+ instance = new
9
+ expects(:new).with(*args).returns(instance)
10
+
11
+ class_eval do
12
+ alias initialize original_initialize
13
+ undef original_initialize
14
+ end
15
+
16
+ return instance
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ module Spec::Mocks::Methods
2
+ def mock_instance(*args)
3
+ class_eval do
4
+ alias original_initialize initialize
5
+ def initialize()end
6
+ end
7
+
8
+ instance = new
9
+ should_receive(:new).with(*args).and_return(instance)
10
+
11
+ class_eval do
12
+ alias initialize original_initialize
13
+ undef original_initialize
14
+ end
15
+
16
+ return instance
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ if defined? Mocha
2
+ require File.dirname(__FILE__) + "/mock_instance/mocha"
3
+ elsif defined? Spec
4
+ require File.dirname(__FILE__) + "/mock_instance/rspec"
5
+ else
6
+ raise "Either mocha_standalone or spec/mocks must be required before you can use mock_instance"
7
+ end
data/lib/synthesis.rb ADDED
@@ -0,0 +1,17 @@
1
+ require "set"
2
+
3
+ $: << File.dirname(__FILE__)
4
+
5
+ require "synthesis/logging"
6
+ require "synthesis/recordable"
7
+ require "synthesis/object"
8
+ require "synthesis/class"
9
+ require "synthesis/module"
10
+ require "synthesis/expectation_record"
11
+ require "synthesis/method_invocation_watcher"
12
+ require "synthesis/expectation"
13
+ require "synthesis/expectation_matcher"
14
+ require "synthesis/expectation_interceptor"
15
+ require "synthesis/expectation_record_enabled"
16
+ require "synthesis/reporter"
17
+ require "synthesis/adapter"
data/synthesis.gemspec ADDED
@@ -0,0 +1,41 @@
1
+ GEMSPEC =Gem::Specification.new do |s|
2
+ s.name = 'synthesis'
3
+ s.version = '0.1.6'
4
+ s.platform = Gem::Platform::RUBY
5
+ s.rubyforge_project = "synthesis"
6
+ s.summary, s.description = 'A tool for Synthesized Testing'
7
+ s.authors = 'Stuart Caborn, George Malamidis'
8
+ s.email = 'george@nutrun.com'
9
+ s.homepage = 'http://synthesis.rubyforge.org'
10
+ s.has_rdoc = true
11
+ s.rdoc_options += ['--quiet', '--title', 'Synthesis', '--main', 'README', '--inline-source']
12
+ s.extra_rdoc_files = ['README', 'COPYING']
13
+ s.files = [
14
+ "COPYING",
15
+ "Rakefile",
16
+ "README",
17
+ "synthesis.gemspec",
18
+ "lib/synthesis/adapter/expectations.rb",
19
+ "lib/synthesis/adapter/mocha.rb",
20
+ "lib/synthesis/adapter/rspec.rb",
21
+ "lib/synthesis/adapter.rb",
22
+ "lib/synthesis/class.rb",
23
+ "lib/synthesis/expectation.rb",
24
+ "lib/synthesis/expectation_interceptor.rb",
25
+ "lib/synthesis/expectation_matcher.rb",
26
+ "lib/synthesis/expectation_record.rb",
27
+ "lib/synthesis/expectation_record_enabled.rb",
28
+ "lib/synthesis/logging.rb",
29
+ "lib/synthesis/method_invocation_watcher.rb",
30
+ "lib/synthesis/module.rb",
31
+ "lib/synthesis/object.rb",
32
+ "lib/synthesis/recordable.rb",
33
+ "lib/synthesis/reporter.rb",
34
+ "lib/synthesis/runner.rb",
35
+ "lib/synthesis/task.rb",
36
+ "lib/synthesis/util/mock_instance/mocha.rb",
37
+ "lib/synthesis/util/mock_instance/rspec.rb",
38
+ "lib/synthesis/util/mock_instance.rb",
39
+ "lib/synthesis.rb"
40
+ ]
41
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gmalamid-synthesis
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.6
5
+ platform: ruby
6
+ authors:
7
+ - Stuart Caborn, George Malamidis
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-07-08 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: george@nutrun.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ - COPYING
25
+ files:
26
+ - COPYING
27
+ - Rakefile
28
+ - README
29
+ - synthesis.gemspec
30
+ - lib/synthesis/adapter/expectations.rb
31
+ - lib/synthesis/adapter/mocha.rb
32
+ - lib/synthesis/adapter/rspec.rb
33
+ - lib/synthesis/adapter.rb
34
+ - lib/synthesis/class.rb
35
+ - lib/synthesis/expectation.rb
36
+ - lib/synthesis/expectation_interceptor.rb
37
+ - lib/synthesis/expectation_matcher.rb
38
+ - lib/synthesis/expectation_record.rb
39
+ - lib/synthesis/expectation_record_enabled.rb
40
+ - lib/synthesis/logging.rb
41
+ - lib/synthesis/method_invocation_watcher.rb
42
+ - lib/synthesis/module.rb
43
+ - lib/synthesis/object.rb
44
+ - lib/synthesis/recordable.rb
45
+ - lib/synthesis/reporter.rb
46
+ - lib/synthesis/runner.rb
47
+ - lib/synthesis/task.rb
48
+ - lib/synthesis/util/mock_instance/mocha.rb
49
+ - lib/synthesis/util/mock_instance/rspec.rb
50
+ - lib/synthesis/util/mock_instance.rb
51
+ - lib/synthesis.rb
52
+ has_rdoc: true
53
+ homepage: http://synthesis.rubyforge.org
54
+ post_install_message:
55
+ rdoc_options:
56
+ - --quiet
57
+ - --title
58
+ - Synthesis
59
+ - --main
60
+ - README
61
+ - --inline-source
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ version:
76
+ requirements: []
77
+
78
+ rubyforge_project: synthesis
79
+ rubygems_version: 1.2.0
80
+ signing_key:
81
+ specification_version: 2
82
+ summary: A tool for Synthesized Testing
83
+ test_files: []
84
+