gmalamid-synthesis 0.1.8 → 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
@@ -34,6 +34,8 @@ Synthesis can be setup to ignore certain classes or modules when collecting expe
34
34
 
35
35
  If +pattern+ is not specified, it will default to <tt>test/**/*_test.rb</tt>
36
36
 
37
+ As of version 0.2.0, Synthesis has a +DOT+ formatter which, when used, will output text in the DOT graph description language, producing system visualizations as specified by the simulated interactions in the system's tests. The output of the +DOT+ formatter can be used with tools like Graphviz( http://www.graphviz.org/ ).
38
+
37
39
  == Usage examples
38
40
 
39
41
  To use with Test::Unit and Mocha, ignoring Array and Hash:
@@ -54,7 +56,7 @@ To use with RSpec, running all specs in the <tt>spec</tt> directory:
54
56
  t.pattern = 'spec/**/*_spec.rb'
55
57
  end
56
58
 
57
- To use with Expectations, redirecting output to a file
59
+ To use with Expectations, redirecting output to a file:
58
60
 
59
61
  require "synthesis/task"
60
62
 
@@ -62,6 +64,23 @@ To use with Expectations, redirecting output to a file
62
64
  t.adapter = :expectations
63
65
  t.out = File.new "synthesis.test.txt", "a"
64
66
  end
67
+
68
+ To output a DOT graph:
69
+
70
+ require "synthesis/task"
71
+
72
+ Synthesis::Task.new do |t|
73
+ t.formatter = :dot
74
+ end
75
+
76
+ To use Synthesis with Rails:
77
+
78
+ require "synthesis/task"
79
+
80
+ Synthesis::Task.new do |t|
81
+ RAILS_ENV = "test"
82
+ t.pattern = 'test/**/*_test.rb'
83
+ end
65
84
 
66
85
  == Utilities
67
86
 
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
@@ -16,6 +16,7 @@ module Synthesis
16
16
  Object.extend(ExpectationRecorder)
17
17
  Object.record_expectations_on(:expects)
18
18
  Mocha::Expectation.extend(ExpectationInterceptor)
19
+ Mocha::Expectation.intercept_test_subject_on(:invoke)
19
20
  Mocha::Expectation.intercept_expected_arguments_on(:with)
20
21
  Mocha::Expectation.intercept_expected_return_values_on(:returns)
21
22
  Mocha::Expectation.intercept_expected_return_values_on(:raises)
@@ -26,6 +26,7 @@ module Synthesis
26
26
  Spec::Mocks::Methods.extend(ExpectationRecorder)
27
27
  Spec::Mocks::Methods.record_expectations_on(:should_receive)
28
28
  Spec::Mocks::MessageExpectation.extend(ExpectationInterceptor)
29
+ Spec::Mocks::MessageExpectation.intercept_test_subject_on(:invoke)
29
30
  Spec::Mocks::MessageExpectation.intercept_expected_arguments_on(:with)
30
31
  Spec::Mocks::MessageExpectation.intercept_expected_return_values_on(:and_return)
31
32
  Spec::Mocks::MessageExpectation.intercept_expected_return_values_on(:and_raise)
@@ -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,6 +3,26 @@ 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
28
  def intercept_expected_arguments_on(method_name)
@@ -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
@@ -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
@@ -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
@@ -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/lib/synthesis.rb CHANGED
@@ -14,4 +14,5 @@ require "synthesis/expectation_matcher"
14
14
  require "synthesis/expectation_interceptor"
15
15
  require "synthesis/expectation_recorder"
16
16
  require "synthesis/reporter"
17
+ require "synthesis/formatter"
17
18
  require "synthesis/adapter"
data/synthesis.gemspec CHANGED
@@ -1,10 +1,10 @@
1
1
  GEMSPEC =Gem::Specification.new do |s|
2
2
  s.name = 'synthesis'
3
- s.version = '0.1.8'
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'
7
- s.authors = 'Stuart Caborn, George Malamidis'
7
+ s.authors = 'Stuart Caborn, George Malamidis, Danilo Sato'
8
8
  s.email = 'george@nutrun.com'
9
9
  s.homepage = 'http://synthesis.rubyforge.org'
10
10
  s.has_rdoc = true
@@ -24,6 +24,9 @@ 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/formatter/dot.rb",
28
+ "lib/synthesis/formatter/text.rb",
29
+ "lib/synthesis/formatter.rb",
27
30
  "lib/synthesis/expectation_recorder.rb",
28
31
  "lib/synthesis/logging.rb",
29
32
  "lib/synthesis/method_invocation_watcher.rb",
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gmalamid-synthesis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
- - Stuart Caborn, George Malamidis
7
+ - Stuart Caborn, George Malamidis, Danilo Sato
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-10-09 00:00:00 -07:00
12
+ date: 2008-10-27 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -36,6 +36,9 @@ 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/formatter/dot.rb
40
+ - lib/synthesis/formatter/text.rb
41
+ - lib/synthesis/formatter.rb
39
42
  - lib/synthesis/expectation_recorder.rb
40
43
  - lib/synthesis/logging.rb
41
44
  - lib/synthesis/method_invocation_watcher.rb