rspec-contracts 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ce4b2a2faf48c3f9f368745eed2e0a33243fe872
4
- data.tar.gz: 8f01e23a2eed146f5bbe6407cd44fbaa51700cb8
3
+ metadata.gz: 87b15ff10f754beb89ee5b1d4243b8aea51ca5c3
4
+ data.tar.gz: b28190904c6dc5fd3c909ee73cdbb7c9dd4df3ac
5
5
  SHA512:
6
- metadata.gz: dbd6c1a8c64c9fcd1778ae4596abab1ed6f4d1e8d8015f376b6598ad6eb6ccf2670cb534e24c5149853886ca47620ac8f05964312f65f32e95fe5ed4d5dbf880
7
- data.tar.gz: bad52385149dc5c04ca685fe0551ab9414ec17db7dbbb54faa42330e3d0ccdf9043fe32ebc4e5bc16227abe722874712178e2effbf85d4ec380c83c62e7cca8d
6
+ metadata.gz: 27e3827e0404171952cdc793c8032caf74182e1621b2ad932c1d33cbf116ab63fd4ea8fcbbb4bde6bc8493f08c4ce4b9912341a02780b6d8e1eb2cb79cefe738
7
+ data.tar.gz: f3f091d6f3f01819b6122727b678992068ef2304f59e7036777ef4ce49200d43c9352ce106a9b95e12d5c8b0e71da657037da8ba921aece444f346db428246ec
data/History.md CHANGED
@@ -1,3 +1,8 @@
1
+ ### Version 0.0.2
2
+ 2014-3-15
3
+
4
+ * Verify contracts by observing arguments and return values
5
+
1
6
  ### Version 0.0.1
2
7
  2014-3-4
3
8
 
data/README.md CHANGED
@@ -1,6 +1,9 @@
1
- ## Conjure
1
+ ## rspec-contracts
2
+ [![Gem Version](https://badge.fury.io/rb/rspec-contracts.png)](http://badge.fury.io/rb/rspec-contracts)
3
+ [![Build Status](https://travis-ci.org/brianauton/rspec-contracts.png?branch=master)](https://travis-ci.org/brianauton/rspec-contracts)
4
+ [![Code Climate](https://codeclimate.com/github/brianauton/rspec-contracts.png)](https://codeclimate.com/github/brianauton/rspec-contracts)
2
5
 
3
- Automatic contract checking for your RSpec suite
6
+ Automatic contract verification for your RSpec suite
4
7
 
5
8
  ### Requirements
6
9
 
@@ -1,12 +1,14 @@
1
- require "rspec/core/example_group"
1
+ require "rspec/core"
2
+ require "rspec/contracts/interface"
2
3
  require "rspec/contracts/interface_proxy"
3
4
 
4
5
  module RSpec
5
6
  module Core
6
7
  class ExampleGroup
7
8
  def self.fulfill_contract(*interface_names)
8
- interface_names.each do |interface_name|
9
- Contracts::InterfaceProxy.create interface_name, described_class
9
+ interface_names.each do |name|
10
+ interface = Contracts::Interface.find_or_create name
11
+ Contracts::InterfaceProxy.create interface, described_class
10
12
  end
11
13
  end
12
14
  end
@@ -1,5 +1,6 @@
1
- require "rspec/mocks/test_double"
2
- require "rspec/contracts/proxy"
1
+ require "rspec/mocks"
2
+ require "rspec/contracts/interface"
3
+ require "rspec/contracts/mock_proxy"
3
4
 
4
5
  module RSpec
5
6
  module Contracts
@@ -7,12 +8,12 @@ module RSpec
7
8
  include Mocks::TestDouble
8
9
 
9
10
  def initialize(interface_name, *args)
10
- @interface_name = interface_name
11
- __initialize_as_test_double interface_name, *args
11
+ @interface = Interface.find_or_create interface_name
12
+ super
12
13
  end
13
14
 
14
15
  def __build_mock_proxy(order_group)
15
- Proxy.new self, order_group, @interface_name
16
+ MockProxy.new self, order_group, @interface
16
17
  end
17
18
  end
18
19
  end
@@ -1,23 +1,31 @@
1
- require "rspec/contracts/requirement"
2
- require "rspec/contracts/requirement_view"
1
+ require "rspec/contracts/interface_fulfillment"
3
2
 
4
3
  module RSpec
5
4
  module Contracts
6
5
  class Fulfillment
7
- def initialize(requirement_group)
8
- @requirement_group = requirement_group
6
+ attr_reader :interface_fulfillments
7
+
8
+ def initialize(interfaces, implementors)
9
+ @implementors = implementors
10
+ @interface_fulfillments = interfaces.map do |interface|
11
+ InterfaceFulfillment.new interface, implementors_for(interface)
12
+ end
9
13
  end
10
14
 
11
15
  def complete?
12
- false
16
+ incomplete_interfaces.empty?
17
+ end
18
+
19
+ def incomplete_interfaces
20
+ interface_fulfillments.reject(&:complete?)
13
21
  end
14
22
 
15
- def unfulfilled_requirements
16
- @requirement_group.requirements
23
+ def messages_count
24
+ interface_fulfillments.map(&:messages_count).inject(&:+) || 0
17
25
  end
18
26
 
19
- def requirements_count
20
- @requirement_group.requirements.count
27
+ def implementors_for(interface)
28
+ @implementors.select{ |i| i.interface_names.include? interface.name }
21
29
  end
22
30
  end
23
31
  end
@@ -1,3 +1,5 @@
1
+ require "rspec/contracts/message_view"
2
+
1
3
  module RSpec
2
4
  module Contracts
3
5
  class FulfillmentView
@@ -6,27 +8,25 @@ module RSpec
6
8
  end
7
9
 
8
10
  def render
9
- @fulfillment.complete? ? render_complete : render_incomplete
10
- end
11
-
12
- def render_complete
13
- "#{contracts_count} verified."
11
+ ([summary] + unfulfilled_views).join "\n"
14
12
  end
15
13
 
16
- def render_incomplete
17
- lines = unfulfilled_views
18
- lines.unshift("WARNING: #{unfulfilled_views.count} of #{contracts_count} unverified.")
19
- lines.join "\n"
14
+ def summary
15
+ unverified = unfulfilled_views.count
16
+ verified = @fulfillment.messages_count - unverified
17
+ "#{contracts_count}, #{verified} verified, #{unverified} unverified"
20
18
  end
21
19
 
22
20
  def unfulfilled_views
23
- @fulfillment.unfulfilled_requirements.map do |requirement|
24
- RSpec::Contracts::RequirementView.new(requirement).render
25
- end
21
+ @fulfillment.incomplete_interfaces.map do |fulfillment|
22
+ fulfillment.unfulfilled_messages.map do |message|
23
+ RSpec::Contracts::MessageView.new(fulfillment.interface.name, message).render
24
+ end
25
+ end.flatten
26
26
  end
27
27
 
28
28
  def contracts_count
29
- pluralize @fulfillment.requirements_count, "contract"
29
+ pluralize @fulfillment.messages_count, "contract"
30
30
  end
31
31
 
32
32
  def pluralize(number, noun)
@@ -0,0 +1,28 @@
1
+ module RSpec
2
+ module Contracts
3
+ class Implementor
4
+ attr_reader :interface_names, :messages
5
+
6
+ def initialize
7
+ @interface_names = []
8
+ @messages = []
9
+ end
10
+
11
+ def add_message(message)
12
+ messages << message
13
+ end
14
+
15
+ def self.collection
16
+ @collection ||= {}
17
+ end
18
+
19
+ def self.all
20
+ collection.values
21
+ end
22
+
23
+ def self.find_or_create(subject)
24
+ collection[subject] ||= new
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ require "rspec/contracts/interface_group"
2
+
3
+ module RSpec
4
+ module Contracts
5
+ class Interface
6
+ attr_reader :name, :messages
7
+
8
+ def initialize(name)
9
+ @name = name
10
+ @messages = []
11
+ end
12
+
13
+ def add_message(message)
14
+ @messages << message
15
+ end
16
+
17
+ def self.all
18
+ @all ||= InterfaceGroup.new
19
+ end
20
+
21
+ def self.find_or_create(name)
22
+ all.find_or_create name
23
+ end
24
+
25
+ def unique_messages
26
+ messages.uniq(&:to_hash)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,34 @@
1
+ module RSpec
2
+ module Contracts
3
+ class InterfaceFulfillment
4
+ attr_reader :interface
5
+
6
+ def initialize(interface, implementors)
7
+ @interface = interface
8
+ @implementors = implementors
9
+ end
10
+
11
+ def complete?
12
+ unfulfilled_messages.empty?
13
+ end
14
+
15
+ def unfulfilled_messages
16
+ interface.unique_messages.reject{ |r| fulfilled_by_all? r }
17
+ end
18
+
19
+ def messages_count
20
+ interface.unique_messages.count
21
+ end
22
+
23
+ private
24
+
25
+ def fulfilled_by_all?(message)
26
+ @implementors.all?{ |i| fulfilled_by? message, i }
27
+ end
28
+
29
+ def fulfilled_by?(message, implementor)
30
+ implementor.messages.any? { |m| message.fully_described_by? m }
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,21 @@
1
+ require "rspec/contracts/interface"
2
+
3
+ module RSpec
4
+ module Contracts
5
+ class InterfaceGroup
6
+ include Enumerable
7
+
8
+ def initialize
9
+ @collection = {}
10
+ end
11
+
12
+ def find_or_create(name)
13
+ @collection[name] ||= Interface.new(name)
14
+ end
15
+
16
+ def each(&block)
17
+ @collection.values.each(&block)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,11 +1,14 @@
1
+ require "rspec/contracts/implementor"
1
2
  require "rspec/contracts/method_proxy"
2
3
 
3
4
  module RSpec
4
5
  module Contracts
5
6
  class InterfaceProxy
6
- def initialize(interface_name, proxied_class)
7
+ def initialize(interface, proxied_class)
8
+ @implementor = Implementor.find_or_create proxied_class
9
+ @implementor.interface_names << interface.name
7
10
  proxied_class.instance_methods.select{|m| proxyable_method? m}.each do |method_name|
8
- MethodProxy.create interface_name, proxied_class, method_name
11
+ MethodProxy.create @implementor, proxied_class, method_name
9
12
  end
10
13
  end
11
14
 
@@ -0,0 +1,30 @@
1
+ module RSpec
2
+ module Contracts
3
+ class Message
4
+ attr_reader :method_name
5
+ attr_accessor :specifications
6
+
7
+ def initialize(method_name, specifications = {})
8
+ @method_name = method_name
9
+ @specifications = specifications
10
+ end
11
+
12
+ def fully_described_by?(message)
13
+ return false if message.method_name != method_name
14
+ @specifications.each do |name, specification|
15
+ unless specification.fully_described_by? message.specifications[name]
16
+ return false
17
+ end
18
+ end
19
+ true
20
+ end
21
+
22
+ def to_hash
23
+ {
24
+ :method_name => method_name,
25
+ :specifications => specifications.values.map(&:to_hash),
26
+ }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ module RSpec
2
+ module Contracts
3
+ class MessageArguments
4
+ attr_reader :arguments
5
+
6
+ def initialize(arguments)
7
+ @arguments = arguments
8
+ end
9
+
10
+ def fully_described_by?(other)
11
+ other && (arguments == other.arguments)
12
+ end
13
+
14
+ def to_hash
15
+ {:arguments => arguments}
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module RSpec
2
+ module Contracts
3
+ class MessageReturn
4
+ attr_reader :value
5
+
6
+ def initialize(value)
7
+ @value = value
8
+ end
9
+
10
+ def fully_described_by?(other)
11
+ other && (value == other.value)
12
+ end
13
+
14
+ def to_hash
15
+ {:value => value}
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ module RSpec
2
+ module Contracts
3
+ class MessageView
4
+ def initialize(interface_name, message)
5
+ @interface_name = interface_name
6
+ @message = message
7
+ end
8
+
9
+ def render
10
+ arg_string = @message.specifications[:arguments] ? "()" : ""
11
+ return_string = @message.specifications[:return_value] ? "and return #{@message.specifications[:return_value].value.inspect}" : ""
12
+ "Interface '#{@interface_name}' must respond to '#{@message.method_name}#{arg_string}' #{return_string}"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,6 @@
1
+ require "rspec/contracts/message"
2
+ require "rspec/contracts/message_arguments"
3
+
1
4
  module RSpec
2
5
  module Contracts
3
6
  class MethodProxy
@@ -5,10 +8,16 @@ module RSpec
5
8
  new(*args)
6
9
  end
7
10
 
11
+ def add_message(options)
12
+ message = Message.new(@method_name, options)
13
+ @implementor.add_message message
14
+ message
15
+ end
16
+
8
17
  private
9
18
 
10
- def initialize(interface_name, proxied_class, method_name)
11
- @interface_name = interface_name
19
+ def initialize(implementor, proxied_class, method_name)
20
+ @implementor = implementor
12
21
  @proxied_class = proxied_class
13
22
  @method_name = method_name
14
23
  install
@@ -16,8 +25,12 @@ module RSpec
16
25
 
17
26
  def install
18
27
  original_method = @proxied_class.instance_method @method_name
28
+ method_proxy = self
19
29
  @proxied_class.send :define_method, @method_name do |*args|
20
- original_method.bind(self).call(*args)
30
+ message = method_proxy.add_message :arguments => MessageArguments.new(args)
31
+ return_value = original_method.bind(self).call(*args)
32
+ message.specifications[:return_value] = MessageReturn.new(return_value)
33
+ return_value
21
34
  end
22
35
  end
23
36
  end
@@ -0,0 +1,51 @@
1
+ require "rspec/mocks"
2
+ require "rspec/contracts/message"
3
+ require "rspec/contracts/message_return"
4
+
5
+ module RSpec
6
+ module Contracts
7
+ class MockProxy < Mocks::Proxy
8
+ def initialize(object, order_group, interface)
9
+ super(object, order_group)
10
+ @method_doubles = Hash.new do |h, k|
11
+ h[k] = ContractMethodDouble.new(interface, object, k, self)
12
+ end
13
+ end
14
+ end
15
+
16
+ class ContractMethodDouble < Mocks::MethodDouble
17
+ attr_reader :message
18
+
19
+ def initialize(interface, object, method_name, proxy)
20
+ @message = Message.new method_name
21
+ interface.add_message @message
22
+ super(object, method_name, proxy)
23
+ end
24
+
25
+ def add_simple_stub(method_name, return_value)
26
+ @message.specifications[:return_value] = MessageReturn.new(return_value)
27
+ super
28
+ end
29
+
30
+ def message_expectation_class
31
+ ContractMessageExpectation
32
+ end
33
+ end
34
+
35
+ class ContractMessageExpectation < Mocks::MessageExpectation
36
+ def contract_message
37
+ @method_double.message
38
+ end
39
+
40
+ def with(*args)
41
+ contract_message.specifications[:arguments] = MessageArguments.new(args)
42
+ super
43
+ end
44
+
45
+ def and_return(*args)
46
+ contract_message.specifications[:return_value] = MessageReturn.new(args.first)
47
+ super
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,3 +1,4 @@
1
+ require "rspec/mocks"
1
2
  require "rspec/contracts/double"
2
3
 
3
4
  module RSpec
@@ -1,5 +1,5 @@
1
1
  module RSpec
2
2
  module Contracts
3
- VERSION = "0.0.1"
3
+ VERSION = "0.0.2"
4
4
  end
5
5
  end
@@ -2,13 +2,15 @@ require "rspec/contracts/core_syntax"
2
2
  require "rspec/contracts/mocks_syntax"
3
3
  require "rspec/contracts/fulfillment"
4
4
  require "rspec/contracts/fulfillment_view"
5
- require "rspec/contracts/requirement"
5
+ require "rspec/contracts/implementor"
6
+ require "rspec/contracts/interface"
6
7
  require "rspec/core"
7
8
 
8
9
  RSpec.configure do |c|
9
10
  c.after(:suite) do
10
- requirement_group = RSpec::Contracts::Requirement.group
11
- fulfillment = RSpec::Contracts::Fulfillment.new requirement_group
11
+ interfaces = RSpec::Contracts::Interface.all
12
+ implementors = RSpec::Contracts::Implementor.all
13
+ fulfillment = RSpec::Contracts::Fulfillment.new interfaces, implementors
12
14
  print "\n" + RSpec::Contracts::FulfillmentView.new(fulfillment).render
13
15
  end
14
16
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-contracts
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Auton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-05 00:00:00.000000000 Z
11
+ date: 2014-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -28,28 +28,28 @@ dependencies:
28
28
  name: guard-rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  description:
@@ -67,13 +67,18 @@ files:
67
67
  - lib/rspec/contracts/double.rb
68
68
  - lib/rspec/contracts/fulfillment.rb
69
69
  - lib/rspec/contracts/fulfillment_view.rb
70
+ - lib/rspec/contracts/implementor.rb
71
+ - lib/rspec/contracts/interface.rb
72
+ - lib/rspec/contracts/interface_fulfillment.rb
73
+ - lib/rspec/contracts/interface_group.rb
70
74
  - lib/rspec/contracts/interface_proxy.rb
75
+ - lib/rspec/contracts/message.rb
76
+ - lib/rspec/contracts/message_arguments.rb
77
+ - lib/rspec/contracts/message_return.rb
78
+ - lib/rspec/contracts/message_view.rb
71
79
  - lib/rspec/contracts/method_proxy.rb
80
+ - lib/rspec/contracts/mock_proxy.rb
72
81
  - lib/rspec/contracts/mocks_syntax.rb
73
- - lib/rspec/contracts/proxy.rb
74
- - lib/rspec/contracts/requirement.rb
75
- - lib/rspec/contracts/requirement_group.rb
76
- - lib/rspec/contracts/requirement_view.rb
77
82
  - lib/rspec/contracts/version.rb
78
83
  homepage: http://github.com/brianauton/rspec-contracts
79
84
  licenses:
@@ -1,32 +0,0 @@
1
- require "rspec/mocks/proxy"
2
- require "rspec/contracts/requirement"
3
-
4
- module RSpec
5
- module Contracts
6
- class Proxy < Mocks::Proxy
7
- def initialize(object, order_group, interface_name)
8
- @interface_name = interface_name
9
- super(object, order_group)
10
- end
11
-
12
- def add_stub(location, method_name, opts={}, &implementation)
13
- create_requirement method_name
14
- super
15
- end
16
-
17
- def add_simple_stub(method_name, *args)
18
- create_requirement method_name, :return_value => args.first
19
- super
20
- end
21
-
22
- def add_message_expectation(location, method_name, opts={}, &block)
23
- create_requirement method_name
24
- super
25
- end
26
-
27
- def create_requirement(method_name, options = {})
28
- Requirement.create @interface_name, method_name, options
29
- end
30
- end
31
- end
32
- end
@@ -1,31 +0,0 @@
1
- require "rspec/contracts/requirement_group"
2
- require "rspec/contracts/requirement_view"
3
-
4
- module RSpec
5
- module Contracts
6
- class Requirement
7
- attr_reader :interface_name, :method_name, :arguments, :return_value
8
-
9
- def initialize(interface_name, method_name, options = {})
10
- @interface_name = interface_name
11
- @method_name = method_name
12
- @arguments = options[:arguments]
13
- @return_value = options[:return_value]
14
- end
15
-
16
- def self.group
17
- @group ||= RequirementGroup.new
18
- end
19
-
20
- def self.create(*args)
21
- group.add new(*args)
22
- end
23
-
24
- def matches?(requirement)
25
- [:interface_name, :method_name, :arguments, :return_value].select do |attribute|
26
- requirement.send(attribute) != send(attribute)
27
- end.empty?
28
- end
29
- end
30
- end
31
- end
@@ -1,19 +0,0 @@
1
- module RSpec
2
- module Contracts
3
- class RequirementGroup
4
- attr_accessor :requirements
5
-
6
- def initialize
7
- @requirements = []
8
- end
9
-
10
- def exists?(requirement)
11
- @requirements.any?{|r| r.matches? requirement}
12
- end
13
-
14
- def add(requirement)
15
- requirements << requirement unless exists? requirement
16
- end
17
- end
18
- end
19
- end
@@ -1,15 +0,0 @@
1
- module RSpec
2
- module Contracts
3
- class RequirementView
4
- def initialize(requirement)
5
- @requirement = requirement
6
- end
7
-
8
- def render
9
- arg_string = @requirement.arguments ? "()" : ""
10
- return_string = @requirement.return_value ? "and return #{@requirement.return_value.inspect}" : ""
11
- "Interface '#{@requirement.interface_name}' must respond to '#{@requirement.method_name}#{arg_string}' #{return_string}"
12
- end
13
- end
14
- end
15
- end