synthesis 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README CHANGED
@@ -92,7 +92,7 @@ Reporting the location (filename and line number) of tested/untested expectation
92
92
 
93
93
  == Contributors
94
94
 
95
- Danilo Sato, Paul Nasrat
95
+ Danilo Sato, Paul Nasrat, Jerome Riga
96
96
 
97
97
  == Discuss
98
98
 
data/Rakefile CHANGED
@@ -1,6 +1,5 @@
1
1
  require "rubygems"
2
2
  require "rake/testtask"
3
- require "rake/gempackagetask"
4
3
  require 'rake/rdoctask'
5
4
  require 'rake/contrib/sshpublisher'
6
5
  require File.dirname(__FILE__) + "/lib/synthesis/task"
@@ -27,15 +26,17 @@ Rake::TestTask.new('test:spec') do |t|
27
26
  t.pattern = 'test/synthesis/adapter/rspec/*_test.rb'
28
27
  end
29
28
 
29
+ # Synthesis test_project tasks
30
+
30
31
  Synthesis::Task.new do |t|
31
32
  t.pattern = 'test_project/mocha/test/*_test.rb'
32
33
  t.ignored = [Array, Hash]
33
34
  # t.out = File.new('synthesis.test.txt', 'a')
34
35
  end
35
36
 
36
- Synthesis::Task.new('synthesis:expectations') do |t|
37
- t.adapter = :expectations
38
- t.pattern = 'test_project/expectations/test/*_test.rb'
37
+ Synthesis::Task.new('synthesis:test:graph') do |t|
38
+ t.pattern = 'test_project/mocha/test/*_test.rb'
39
+ t.formatter = :dot
39
40
  end
40
41
 
41
42
  Synthesis::Task.new('synthesis:spec') do |t|
@@ -43,6 +44,17 @@ Synthesis::Task.new('synthesis:spec') do |t|
43
44
  t.pattern = 'test_project/rspec/*_spec.rb'
44
45
  end
45
46
 
47
+ Synthesis::Task.new('synthesis:spec:graph') do |t|
48
+ t.adapter = :rspec
49
+ t.pattern = 'test_project/rspec/*_spec.rb'
50
+ t.formatter = :dot
51
+ end
52
+
53
+ Synthesis::Task.new('synthesis:expectations') do |t|
54
+ t.adapter = :expectations
55
+ t.pattern = 'test_project/expectations/test/*_test.rb'
56
+ end
57
+
46
58
  namespace :test_project do
47
59
  task :all do
48
60
  STDOUT.puts `rake test_project:mocha`
@@ -78,14 +90,6 @@ task :publish_rdoc do
78
90
  Rake::SshDirPublisher.new("gmalamid@rubyforge.org", "/var/www/gforge-projects/synthesis", "doc").upload
79
91
  end
80
92
 
81
- Rake::GemPackageTask.new(GEMSPEC) do |t|
82
- t.need_zip = false
83
- t.need_tar = false
84
- end
85
-
86
- desc "Remove rdoc and package artefacts"
87
- task :clean => %w[clobber_package clobber_rdoc]
88
-
89
93
  task(:lf) {p Dir["lib/**/*rb"]}
90
94
 
91
95
  task(:check_gemspec) do
data/lib/synthesis.rb CHANGED
@@ -12,6 +12,7 @@ require "synthesis/method_invocation_watcher"
12
12
  require "synthesis/expectation"
13
13
  require "synthesis/expectation_matcher"
14
14
  require "synthesis/expectation_interceptor"
15
- require "synthesis/expectation_record_enabled"
15
+ require "synthesis/expectation_recorder"
16
16
  require "synthesis/reporter"
17
+ require "synthesis/formatter"
17
18
  require "synthesis/adapter"
@@ -12,12 +12,12 @@ module Synthesis
12
12
 
13
13
  def collect_expectations
14
14
  ignore_instances_of Class::AnyInstance
15
- Object.extend(ExpectationRecordEnabled)
15
+ Object.extend(ExpectationRecorder)
16
16
  Object.record_expectations_on(:expects)
17
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)
18
+ Mocha::Expectation.intercept_expected_arguments_on(:with)
19
+ Mocha::Expectation.intercept_expected_return_values_on(:returns)
20
+ Mocha::Expectation.intercept_expected_return_values_on(:raises)
21
21
  Mocha::Expectation.remove_expectation_on(:never)
22
22
  end
23
23
 
@@ -13,12 +13,13 @@ module Synthesis
13
13
 
14
14
  def collect_expectations
15
15
  ignore_instances_of Class::AnyInstance
16
- Object.extend(ExpectationRecordEnabled)
16
+ Object.extend(ExpectationRecorder)
17
17
  Object.record_expectations_on(:expects)
18
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)
19
+ Mocha::Expectation.intercept_test_subject_on(:invoke)
20
+ Mocha::Expectation.intercept_expected_arguments_on(:with)
21
+ Mocha::Expectation.intercept_expected_return_values_on(:returns)
22
+ Mocha::Expectation.intercept_expected_return_values_on(:raises)
22
23
  Mocha::Expectation.remove_expectation_on(:never)
23
24
  end
24
25
 
@@ -7,22 +7,29 @@ require File.dirname(__FILE__) + "/../../synthesis"
7
7
  module Synthesis
8
8
  class RSpecAdapter < Adapter
9
9
  def run
10
- Synthesis.rspec_runner_options.files.clear
11
10
  fail_unless do
12
- Synthesis.rspec_runner_options.instance_variable_set(:@formatters, nil)
11
+ rspec_options = begin
12
+ Spec::Runner.options
13
+ rescue
14
+ rspec_options
15
+ end
16
+
17
+ rspec_options.files.clear
18
+ rspec_options.instance_variable_set(:@formatters, nil)
19
+ rspec_options.run_examples
13
20
  # Synthesis.rspec_runner_options.instance_variable_set(:@format_options, [["profile", STDOUT]])
14
- Synthesis.rspec_runner_options.run_examples
15
21
  end
16
22
  end
17
23
 
18
24
  def collect_expectations
19
25
  ignore_instances_of Spec::Mocks::Mock
20
- Spec::Mocks::Methods.extend(ExpectationRecordEnabled)
26
+ Spec::Mocks::Methods.extend(ExpectationRecorder)
21
27
  Spec::Mocks::Methods.record_expectations_on(:should_receive)
22
28
  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)
29
+ Spec::Mocks::MessageExpectation.intercept_test_subject_on(:invoke)
30
+ Spec::Mocks::MessageExpectation.intercept_expected_arguments_on(:with)
31
+ Spec::Mocks::MessageExpectation.intercept_expected_return_values_on(:and_return)
32
+ Spec::Mocks::MessageExpectation.intercept_expected_return_values_on(:and_raise)
26
33
  Spec::Mocks::MessageExpectation.remove_expectation_on(:never)
27
34
  end
28
35
 
@@ -31,10 +38,4 @@ module Synthesis
31
38
  Spec::Mocks::Methods.stop_recording!
32
39
  end
33
40
  end
34
-
35
- def rspec_runner_options
36
- Spec::Runner.options rescue rspec_options
37
- end
38
-
39
- module_function :rspec_runner_options
40
41
  end
@@ -20,10 +20,19 @@ module Synthesis
20
20
  meta_receiver.recordable_method(@method)
21
21
  end
22
22
 
23
+ def add_test_subject(test_subject)
24
+ (@callers ||= []) << test_subject
25
+ end
26
+
27
+ def test_subject
28
+ @callers[0]
29
+ end
30
+
23
31
  def explode
24
32
  if @return_values.size > 1
25
33
  @return_values.map do |v|
26
34
  expectation = self.class.new(@receiver, @method, @track, @args, [])
35
+ expectation.add_test_subject(@callers.shift)
27
36
  expectation.add_return_values(v)
28
37
  expectation
29
38
  end
@@ -79,11 +88,9 @@ module Synthesis
79
88
  @receiver
80
89
  end
81
90
 
82
- def to_s
83
- "(#{return_value_type}) " +
84
- "#{@receiver.name}.#{@method}(#{@args.map { |arg| arg.class } * ', '})" +
85
- "in #{@track}"
86
- end
91
+ def receiver_repr
92
+ @receiver.name
93
+ end
87
94
  end
88
95
 
89
96
  class Instance < Expectation
@@ -99,11 +106,9 @@ module Synthesis
99
106
  meta_receiver
100
107
  end
101
108
 
102
- def to_s
103
- "(#{return_value_type}) #{meta_receiver.name}.new.#{@method}" +
104
- "(#{@args.map { |arg| arg.class } * ', '})" +
105
- "in #{@track}"
106
- end
109
+ def receiver_repr
110
+ "#{meta_receiver.name}.new"
111
+ end
107
112
  end
108
113
 
109
114
  class NilExpectation < Expectation
@@ -3,9 +3,29 @@ module Synthesis
3
3
  # Synthesis to tap into it in order to collect simulated method arguments
4
4
  # and return values.
5
5
  module ExpectationInterceptor
6
+ # Intercept the actual mock proxy to record the test subject so that
7
+ # Synthesis can track which object is being tested
8
+ def intercept_test_subject_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_invoke_method_name) {method_name}
15
+
16
+ def temp_invoke(*expected_parameters, &matching_block)
17
+ synthesis_expectation.add_test_subject(caller(2)) if synthesis_expectation
18
+ send("intercepted_#{get_invoke_method_name}", *expected_parameters, &matching_block)
19
+ end
20
+
21
+ alias_method method_name, :temp_invoke
22
+ undef temp_invoke
23
+ end
24
+ end
25
+
6
26
  # Intercept the mock object framework's expectation method for declaring a mocked
7
27
  # method's arguments so that Synthesis can record them.
8
- def record_expected_arguments_on(method_name)
28
+ def intercept_expected_arguments_on(method_name)
9
29
  (@original_methods ||= []) << method_name
10
30
 
11
31
  class_eval do
@@ -25,7 +45,7 @@ module Synthesis
25
45
 
26
46
  # Intercept the mock object framework's expectation method for declaring a mocked
27
47
  # method's return values so that Synthesis can record them.
28
- def record_expected_return_values_on(method_name)
48
+ def intercept_expected_return_values_on(method_name)
29
49
  (@original_methods ||= []) << method_name
30
50
 
31
51
  class_eval do
@@ -66,6 +86,8 @@ module Synthesis
66
86
  class_eval do
67
87
  remove_method :synthesis_expectation
68
88
  remove_method :synthesis_expectation=
89
+ remove_method :get_invoke_method_name
90
+ remove_method :get_method_name
69
91
  end
70
92
  end
71
93
 
@@ -61,19 +61,9 @@ module Synthesis
61
61
  def untested_expectations
62
62
  expectations.select { |e| !e.invoked? }
63
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 * ', '}"
64
+
65
+ def has_untested_expectations?
66
+ untested_expectations.any?
77
67
  end
78
68
 
79
69
  private
@@ -2,7 +2,7 @@ module Synthesis
2
2
  # Extend by the mock object framework's construct for declaring a
3
3
  # mock object so that Synthesis can tap into it in order to record
4
4
  # the expectation.
5
- module ExpectationRecordEnabled
5
+ module ExpectationRecorder
6
6
  # Intercept the mock object framework's method for declaring a mock
7
7
  # object so that Synthesis can record it.
8
8
  def record_expectations_on(method_name)
@@ -25,7 +25,7 @@ module Synthesis
25
25
  end
26
26
  end
27
27
 
28
- # Restore the original methods ExpectationRecordEnabled has rewritten and
28
+ # Restore the original methods ExpectationRecorder has rewritten and
29
29
  # undefine their intercepted counterparts.
30
30
  def stop_recording!
31
31
  method_name = @original_expects
@@ -0,0 +1,21 @@
1
+ module Synthesis
2
+ class Formatter
3
+ def report_tested_expectations
4
+ ExpectationRecord.tested_expectations.each { |e| puts e.to_report }
5
+ end
6
+
7
+ def report_untested_expectations
8
+ ExpectationRecord.untested_expectations.each { |e| puts e.to_report }
9
+ end
10
+
11
+ class << self
12
+ def load
13
+ @formatter.new
14
+ end
15
+
16
+ def inherited(subclass)
17
+ @formatter = subclass
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,91 @@
1
+ require "parse_tree"
2
+ require "sexp"
3
+ require "sexp_processor"
4
+
5
+ module Synthesis
6
+ class DotFormatter < Formatter
7
+ def initialize
8
+ Expectation::Expectation.send(:include, ExpectationReportFormat::Dot)
9
+ end
10
+
11
+ def digraph
12
+ puts "digraph synthesis_expectations {"
13
+ puts " rankdir=LR;"
14
+ puts " size=\"8,10\";"
15
+ puts " ratio=\"fill\";"
16
+ puts " node [shape = circle];"
17
+ puts " edge [color = green]"
18
+ report_tested_expectations
19
+ puts
20
+ puts " edge [color = red]"
21
+ report_untested_expectations
22
+ puts "}"
23
+ end
24
+ alias format_failure digraph
25
+ alias format_success digraph
26
+ end
27
+
28
+ module ExpectationReportFormat
29
+ module Dot
30
+ def to_report
31
+ " \"#{test_subject_name}\" -> \"#{receiver_class}\" " +
32
+ "[ label = \"(#{return_value_type}) #{method}(#{arg_types * ', '})\" ];"
33
+ end
34
+
35
+ private
36
+
37
+ def test_subject_name
38
+ filename, line, method = test_subject[1].split(':')
39
+ method = method.scan(/`(.*)'/)[0][0]
40
+ ruby = File.read(filename)
41
+ parser = ParseTree.new
42
+ sexp = parser.parse_tree_for_string(ruby, filename).first
43
+ sexp = Sexp.from_array(sexp)
44
+ return DotProcessor.process(sexp, method)
45
+ rescue
46
+ filename ? "#{filename} (#{line})" : "?"
47
+ end
48
+
49
+ class DotProcessor < SexpProcessor
50
+ attr_accessor :method
51
+ attr_reader :klazz
52
+
53
+ def self.process(exp, method)
54
+ analyzer = self.new
55
+ analyzer.method = method
56
+ analyzer.process(exp)
57
+ analyzer.klazz
58
+ end
59
+
60
+ def initialize
61
+ super
62
+ self.strict = false
63
+ self.auto_shift_type = true
64
+ @ancestors = []
65
+ end
66
+
67
+ def process_module(exp)
68
+ name = exp.shift
69
+ @ancestors.push name
70
+ result = s(:module, name, process(exp.shift))
71
+ @ancestors.pop
72
+ result
73
+ end
74
+
75
+ def process_class(exp)
76
+ name = exp.shift
77
+ @ancestors.push name
78
+ result = s(:class, name, exp.shift, process(exp.shift))
79
+ @ancestors.pop
80
+ result
81
+ end
82
+
83
+ def process_defn(exp)
84
+ name = exp.shift
85
+ @klazz = @ancestors * '::' if name == method.to_sym
86
+ s(:defn, name, process(exp.shift), process(exp.shift))
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,32 @@
1
+ module Synthesis
2
+ class TextFormatter < Formatter
3
+ include Logging
4
+
5
+ def initialize
6
+ Expectation::Expectation.send(:include, ExpectationReportFormat::Text)
7
+ end
8
+
9
+ def format_success
10
+ log; log "Verified #{ExpectationRecord.expectations.size} expectations"
11
+ log "SUCCESS."
12
+ end
13
+
14
+ def format_failure
15
+ log; log "Tested Expectations: "
16
+ report_tested_expectations
17
+ log; log "Untested Expectations: "
18
+ report_untested_expectations
19
+ log "Ignoring: #{ExpectationRecord.ignored.to_a * ', '}"
20
+ log; log "FAILED."
21
+ end
22
+ end
23
+
24
+ module ExpectationReportFormat
25
+ module Text
26
+ def to_report
27
+ "(#{return_value_type}) #{receiver_repr}.#{@method}" +
28
+ "(#{arg_types * ', '}) in #{@track}"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,8 +1,15 @@
1
1
  module Synthesis
2
2
  class MethodInvocationWatcher
3
3
  def self.invoked(receiver, method, args = [], return_values = [])
4
+ # cal = caller.clone
5
+ # cal.shift # ignore first (eval)
6
+ # path_from_spec = []
7
+ # begin
8
+ # c = cal.shift
9
+ # path_from_spec.unshift c # unless c =~ /(\(eval\)|gems)/
10
+ # end until c =~ /(spec|test)/
4
11
  matcher = Expectation.new(receiver, method, nil, args, return_values)
5
- ExpectationRecord[matcher].invoked!
12
+ ExpectationRecord[matcher].invoked! # path_from_spec
6
13
  end
7
14
  end
8
15
  end
@@ -1,24 +1,13 @@
1
1
  module Synthesis
2
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?
3
+ def self.report
4
+ formatter = Formatter.load
5
+ if ExpectationRecord.has_untested_expectations?
6
+ formatter.format_failure
7
+ return -1
21
8
  end
9
+ formatter.format_success
10
+ 0
22
11
  end
23
12
  end
24
13
  end
@@ -1,7 +1,8 @@
1
1
  module Synthesis
2
2
  class Runner
3
- def self.run(adapter, pattern)
3
+ def self.run(adapter, pattern, formatter)
4
4
  require "synthesis/adapter/#{adapter}"
5
+ require "synthesis/formatter/#{formatter}"
5
6
  Adapter.load(pattern).run
6
7
  at_exit { Reporter.report unless $! }
7
8
  end
@@ -6,7 +6,8 @@ require File.dirname(__FILE__) + "/../synthesis/logging"
6
6
  module Synthesis
7
7
  class Task < Rake::TaskLib
8
8
  include Logging
9
- attr_accessor :verbose, :pattern, :ruby_opts, :adapter, :out, :ignored, :libs
9
+ attr_accessor :verbose, :pattern, :ruby_opts
10
+ attr_accessor :adapter, :out, :ignored, :libs, :formatter
10
11
 
11
12
  def initialize(name='synthesis:test')
12
13
  @name, @ignored, @libs = name, [], []
@@ -14,6 +15,7 @@ module Synthesis
14
15
  @pattern ||= 'test/**/*_test.rb'
15
16
  @ruby_opts ||= []
16
17
  @adapter ||= :mocha
18
+ @formatter ||= :text
17
19
  define
18
20
  end
19
21
 
@@ -37,7 +39,7 @@ module Synthesis
37
39
  require File.dirname(__FILE__) + "/../synthesis/runner"
38
40
  Synthesis::Logging.const_set(:OUT, @out) if @out
39
41
  Synthesis::ExpectationRecord.ignore(*@ignored)
40
- Synthesis::Runner.run(@adapter, @pattern)
42
+ Synthesis::Runner.run(@adapter, @pattern, @formatter)
41
43
  end
42
44
  end
43
45
  self
@@ -15,4 +15,4 @@ module Spec::Mocks::Methods
15
15
 
16
16
  return instance
17
17
  end
18
- end
18
+ end
data/synthesis.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  GEMSPEC =Gem::Specification.new do |s|
2
2
  s.name = 'synthesis'
3
- s.version = '0.1.7'
3
+ s.version = '0.2.0'
4
4
  s.platform = Gem::Platform::RUBY
5
5
  s.rubyforge_project = "synthesis"
6
6
  s.summary, s.description = 'A tool for Synthesized Testing'
@@ -24,7 +24,10 @@ GEMSPEC =Gem::Specification.new do |s|
24
24
  "lib/synthesis/expectation_interceptor.rb",
25
25
  "lib/synthesis/expectation_matcher.rb",
26
26
  "lib/synthesis/expectation_record.rb",
27
- "lib/synthesis/expectation_record_enabled.rb",
27
+ "lib/synthesis/formatter/dot.rb",
28
+ "lib/synthesis/formatter/text.rb",
29
+ "lib/synthesis/formatter.rb",
30
+ "lib/synthesis/expectation_recorder.rb",
28
31
  "lib/synthesis/logging.rb",
29
32
  "lib/synthesis/method_invocation_watcher.rb",
30
33
  "lib/synthesis/module.rb",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: synthesis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stuart Caborn, George Malamidis
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-10-20 00:00:00 +01:00
12
+ date: 2008-11-01 00:00:00 +00:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -36,7 +36,10 @@ files:
36
36
  - lib/synthesis/expectation_interceptor.rb
37
37
  - lib/synthesis/expectation_matcher.rb
38
38
  - lib/synthesis/expectation_record.rb
39
- - lib/synthesis/expectation_record_enabled.rb
39
+ - lib/synthesis/formatter/dot.rb
40
+ - lib/synthesis/formatter/text.rb
41
+ - lib/synthesis/formatter.rb
42
+ - lib/synthesis/expectation_recorder.rb
40
43
  - lib/synthesis/logging.rb
41
44
  - lib/synthesis/method_invocation_watcher.rb
42
45
  - lib/synthesis/module.rb
@@ -76,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
79
  requirements: []
77
80
 
78
81
  rubyforge_project: synthesis
79
- rubygems_version: 1.2.0
82
+ rubygems_version: 1.3.1
80
83
  signing_key:
81
84
  specification_version: 2
82
85
  summary: A tool for Synthesized Testing