synthesis 0.1.7 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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