armin-joellenbeck-rdbc 0.0.4
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 +99 -0
- data/Rakefile +31 -0
- data/examples/stack.rb +44 -0
- data/examples/stack_contract.rb +36 -0
- data/lib/contract.rb +22 -0
- data/lib/object.rb +13 -0
- data/lib/proxy.rb +46 -0
- data/lib/translate.rb +56 -0
- data/rdbc.gemspec +26 -0
- data/spec/contract_spec.rb +4 -0
- data/spec/object_spec.rb +4 -0
- data/spec/proxy_spec.rb +4 -0
- data/spec/spec.opts +3 -0
- data/spec/stack_contract_spec.rb +4 -0
- data/spec/stack_spec.rb +30 -0
- data/spec/translate_spec.rb +45 -0
- metadata +74 -0
data/README
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
=Design by Contract for Ruby
|
2
|
+
|
3
|
+
This library supports Design by Contract for Ruby.
|
4
|
+
|
5
|
+
=Installation
|
6
|
+
|
7
|
+
Install the *rdbc* gem itself by the following command:
|
8
|
+
|
9
|
+
$ gem install rdbc
|
10
|
+
|
11
|
+
=Usage
|
12
|
+
|
13
|
+
A a standard example for Design by Contract consider the following Stack
|
14
|
+
class and its contract StackContract.
|
15
|
+
|
16
|
+
===stack.rb
|
17
|
+
|
18
|
+
:include:examples/stack.rb
|
19
|
+
|
20
|
+
===stack_contract.rb
|
21
|
+
|
22
|
+
:include:examples/stack_contract.rb
|
23
|
+
|
24
|
+
The mechanics are really simple. There is the class Stack.
|
25
|
+
For instances of this class you can define a contract by subclassing
|
26
|
+
<b>DesignByContract::Contract</b>, in the example the contract is named
|
27
|
+
StackContract.
|
28
|
+
Then connect the class Stack to its contract StackContract by using the line
|
29
|
+
contract StackContract
|
30
|
+
in the class definition of Stack.
|
31
|
+
|
32
|
+
Now the following happens for an instance method of Stack. Say its name is
|
33
|
+
*method*. When it is called the the following calling of methods happens.
|
34
|
+
|
35
|
+
* *method_pre* of the contract is called with the parameters:
|
36
|
+
* the object itself
|
37
|
+
* the parameters of the original method *method*
|
38
|
+
* the original *method* is called
|
39
|
+
* *method_post* of the contract is called with the parameters:
|
40
|
+
* the object before the execution of the original method
|
41
|
+
* the object after execution the original method
|
42
|
+
* the return value of the original method
|
43
|
+
* the parameters of the original method
|
44
|
+
* *invariant* is called with the parameters:
|
45
|
+
* the object before the execution of the original method
|
46
|
+
* the object after execution the method
|
47
|
+
|
48
|
+
The object before the execution of *method* in 3. and 4.
|
49
|
+
is in fact a copy of the object before the execution. So you have to implement
|
50
|
+
the method *initialize_copy* in the class under contract, i.e. the class
|
51
|
+
Stack.
|
52
|
+
|
53
|
+
In any of these contract methods you can use assertions as in Test::Unit tests.
|
54
|
+
When an assertion fails, the exception Test::Unit::AssertionFailedError
|
55
|
+
is raised.
|
56
|
+
|
57
|
+
When the method is an operator the *_pre* and *_post* suffixes are appended
|
58
|
+
to the names of the operator according to the constant #Translate::OPERATOR.
|
59
|
+
|
60
|
+
=Support
|
61
|
+
|
62
|
+
The project home is at
|
63
|
+
GitHub[http://github.com/armin-joellenbeck/rdbc/tree/master]:
|
64
|
+
http://github.com/armin-joellenbeck/rdbc/tree/master
|
65
|
+
|
66
|
+
Feel free to send:
|
67
|
+
* bug reports
|
68
|
+
* support requests
|
69
|
+
* feature requests
|
70
|
+
* patches
|
71
|
+
|
72
|
+
=Copyright
|
73
|
+
|
74
|
+
Copyright (c) 2007 - 2008 by Armin Jöllenbeck
|
75
|
+
<armin@joellenbeck.net[mailto:armin@joellenbeck.net]>
|
76
|
+
|
77
|
+
All rights reserved.
|
78
|
+
|
79
|
+
Redistribution and use in source and binary forms, with or without
|
80
|
+
modification, are permitted provided that the following conditions
|
81
|
+
are met:
|
82
|
+
1. Redistributions of source code must retain the above copyright
|
83
|
+
notice, this list of conditions and the following disclaimer.
|
84
|
+
2. Redistributions in binary form must reproduce the above copyright
|
85
|
+
notice, this list of conditions and the following disclaimer in the
|
86
|
+
documentation and/or other materials provided with the distribution.
|
87
|
+
3. The names of the contributors may not be used to endorse or promote products
|
88
|
+
derived from this software without specific prior written permission.
|
89
|
+
|
90
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
91
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
92
|
+
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
93
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
94
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
95
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
96
|
+
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
97
|
+
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
98
|
+
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
99
|
+
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec/rake/spectask'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
|
6
|
+
task :default => [:spec]
|
7
|
+
|
8
|
+
|
9
|
+
task :all => [:spec, :doc, :package]
|
10
|
+
|
11
|
+
|
12
|
+
Spec::Rake::SpecTask.new do |t|
|
13
|
+
t.libs << 'spec' << 'examples' << 'lib'
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
Rake::RDocTask.new(:doc) do |t|
|
18
|
+
t.rdoc_dir = 'doc'
|
19
|
+
t.rdoc_files.include('README', 'lib/**/*.rb')
|
20
|
+
t.options = [
|
21
|
+
'--all',
|
22
|
+
'--charset', 'utf8',
|
23
|
+
'--inline-source',
|
24
|
+
'--main', 'README',
|
25
|
+
'--title', 'Design by Contract for Ruby'
|
26
|
+
]
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
Rake::GemPackageTask.new(eval(File.read('rdbc.gemspec'))) do |pkg|
|
31
|
+
end
|
data/examples/stack.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'stack_contract'
|
2
|
+
|
3
|
+
class Stack
|
4
|
+
|
5
|
+
contract StackContract
|
6
|
+
|
7
|
+
class NoPopForEmptyStack < RuntimeError
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@elements = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize_copy(orig)
|
15
|
+
@elements = orig.elements
|
16
|
+
end
|
17
|
+
|
18
|
+
def elements
|
19
|
+
@elements.dup
|
20
|
+
end
|
21
|
+
|
22
|
+
def size
|
23
|
+
@elements.size
|
24
|
+
end
|
25
|
+
|
26
|
+
def empty?
|
27
|
+
size == 0
|
28
|
+
end
|
29
|
+
|
30
|
+
def top
|
31
|
+
@elements.last
|
32
|
+
end
|
33
|
+
|
34
|
+
def push(element)
|
35
|
+
@elements.push(element)
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def pop
|
40
|
+
raise NoPopForEmptyStack if empty?
|
41
|
+
@elements.pop
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'contract'
|
2
|
+
|
3
|
+
class StackContract < Contract
|
4
|
+
|
5
|
+
def push_post(stack_pre, stack, exception, result, element)
|
6
|
+
assert_nil(result)
|
7
|
+
assert_equal(element, stack.top)
|
8
|
+
assert_equal(stack.size, stack_pre.size + 1)
|
9
|
+
end
|
10
|
+
|
11
|
+
def pop_pre(stack)
|
12
|
+
assert_operator(stack.size, :>=, 0)
|
13
|
+
end
|
14
|
+
|
15
|
+
def pop_post(stack_pre, stack, exception, result)
|
16
|
+
if stack_pre.empty?
|
17
|
+
assert_kind_of(Stack::NoPopForEmptyStack, exception)
|
18
|
+
else
|
19
|
+
assert_equal(stack_pre.top, result)
|
20
|
+
assert_equal(stack_pre.size - 1, stack.size)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def top_pre(stack)
|
25
|
+
assert_operator(stack.size, :>=, 1)
|
26
|
+
end
|
27
|
+
|
28
|
+
def top_post(stack_pre, stack, exception, result)
|
29
|
+
assert_equal(stack_pre.size, stack.size)
|
30
|
+
end
|
31
|
+
|
32
|
+
def invariant(stack)
|
33
|
+
assert_operator(stack.size, :>=, 0)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/lib/contract.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'test/unit/assertions'
|
2
|
+
|
3
|
+
class Contract
|
4
|
+
include Test::Unit::Assertions
|
5
|
+
end
|
6
|
+
|
7
|
+
|
8
|
+
require 'object'
|
9
|
+
require 'proxy'
|
10
|
+
|
11
|
+
class Class
|
12
|
+
|
13
|
+
def contract(klass)
|
14
|
+
old_new = self.method(:new)
|
15
|
+
self.define_singleton_method(:new) do |*args|
|
16
|
+
object = old_new.call(*args)
|
17
|
+
contract = klass.new
|
18
|
+
@proxy = Proxy.new(object, contract)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
data/lib/object.rb
ADDED
data/lib/proxy.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'translate'
|
2
|
+
|
3
|
+
class Proxy
|
4
|
+
|
5
|
+
def initialize(object, contract)
|
6
|
+
@object = object
|
7
|
+
@contract = contract
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(method, *args)
|
11
|
+
# clone the object under contract for use in the pre condition
|
12
|
+
object_pre = @object.clone
|
13
|
+
|
14
|
+
# call pre condition
|
15
|
+
method_pre = Translate.method_pre(method)
|
16
|
+
if @contract.respond_to?(method_pre)
|
17
|
+
@contract.send(method_pre, @object, *args)
|
18
|
+
end
|
19
|
+
|
20
|
+
# call the wrapped method
|
21
|
+
exception = nil
|
22
|
+
begin
|
23
|
+
result = @object.send(method, *args)
|
24
|
+
rescue => exception
|
25
|
+
end
|
26
|
+
|
27
|
+
# call the invariant condition
|
28
|
+
if @contract.respond_to?(:invariant)
|
29
|
+
@contract.invariant(@object)
|
30
|
+
end
|
31
|
+
|
32
|
+
# call the post condition
|
33
|
+
method_post = Translate.method_post(method)
|
34
|
+
if @contract.respond_to?(method_post)
|
35
|
+
@contract.send(method_post, object_pre, @object, exception, result, *args)
|
36
|
+
end
|
37
|
+
|
38
|
+
# return the return value of the wrapped method call or raise the exception
|
39
|
+
if exception.nil?
|
40
|
+
result
|
41
|
+
else
|
42
|
+
raise exception
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
data/lib/translate.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
module Translate
|
2
|
+
|
3
|
+
OPERATOR = {
|
4
|
+
:[] => :element_read,
|
5
|
+
:[]= => :element_write,
|
6
|
+
:** => :power,
|
7
|
+
:~ => :not,
|
8
|
+
:+@ => :unary_plus,
|
9
|
+
:-@ => :unary_minus,
|
10
|
+
:* => :product,
|
11
|
+
:/ => :quotient,
|
12
|
+
:% => :modulo,
|
13
|
+
:+ => :plus,
|
14
|
+
:- => :minus,
|
15
|
+
:>> => :right_shift,
|
16
|
+
:<< => :left_shift,
|
17
|
+
:& => :and,
|
18
|
+
:^ => :xor,
|
19
|
+
:| => :or,
|
20
|
+
:<= => :less_or_equal,
|
21
|
+
:< => :less,
|
22
|
+
:> => :greater,
|
23
|
+
:>= => :greater_or_equal,
|
24
|
+
:<=> => :comparison,
|
25
|
+
:== => :eql,
|
26
|
+
:=== => :case_comparison,
|
27
|
+
:=~ => :match
|
28
|
+
}
|
29
|
+
|
30
|
+
def self.method_pre(method)
|
31
|
+
method_with_suffix(method, :pre)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.method_post(method)
|
35
|
+
method_with_suffix(method, :post)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.method_with_suffix(method, type)
|
39
|
+
operator = OPERATOR[method]
|
40
|
+
suffix = '_' + type.to_s
|
41
|
+
if operator.nil?
|
42
|
+
method_string = method.to_s
|
43
|
+
length = method_string.length
|
44
|
+
head = method_string[0...length-1]
|
45
|
+
tail = method_string[length-1...length]
|
46
|
+
if ['?', '!', '='].include?(tail)
|
47
|
+
(head + suffix + tail).to_sym
|
48
|
+
else
|
49
|
+
(method_string + suffix).to_sym
|
50
|
+
end
|
51
|
+
else
|
52
|
+
('op_' + operator.to_s + suffix).to_sym
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
data/rdbc.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'rdbc'
|
3
|
+
s.version = '0.0.4'
|
4
|
+
s.summary = 'Design by Contract for Ruby.'
|
5
|
+
s.author = 'Armin Joellenbeck'
|
6
|
+
s.email = 'armin@joellenbeck.net'
|
7
|
+
s.homepage = 'http://github.com/armin-joellenbeck/rdbc/tree/master'
|
8
|
+
s.description = <<-EOF
|
9
|
+
EOF
|
10
|
+
s.files = Dir.glob([
|
11
|
+
'README',
|
12
|
+
'Rakefile',
|
13
|
+
'*.gemspec',
|
14
|
+
'examples/**/*',
|
15
|
+
'lib/**/*',
|
16
|
+
'spec/**/*'
|
17
|
+
])
|
18
|
+
s.has_rdoc = true
|
19
|
+
s.extra_rdoc_files << 'README'
|
20
|
+
s.rdoc_options = [
|
21
|
+
'--all',
|
22
|
+
'--charset', 'utf8',
|
23
|
+
'--main', 'README',
|
24
|
+
'--title', 'Design by Contract for Ruby'
|
25
|
+
]
|
26
|
+
end
|
data/spec/object_spec.rb
ADDED
data/spec/proxy_spec.rb
ADDED
data/spec/spec.opts
ADDED
data/spec/stack_spec.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'stack'
|
2
|
+
|
3
|
+
describe Stack do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@stack = Stack.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should be empty' do
|
10
|
+
@stack.should be_empty
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should have no element on top' do
|
14
|
+
lambda { @stack.top }.should raise_error(Test::Unit::AssertionFailedError)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should not be possible to pop an element' do
|
18
|
+
lambda { @stack.pop }.should raise_error(Stack::NoPopForEmptyStack)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'a pushed element should be on the top' do
|
22
|
+
element = 0
|
23
|
+
@stack.push(element)
|
24
|
+
@stack.should_not be_empty
|
25
|
+
@stack.top.should be_equal(element)
|
26
|
+
@stack.pop.should be_equal(element)
|
27
|
+
@stack.should be_empty
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'translate'
|
2
|
+
|
3
|
+
describe Translate do
|
4
|
+
|
5
|
+
it 'should give the pre method for a normal method' do
|
6
|
+
Translate.method_pre(:method).should == :method_pre
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should give the pre method for a question method' do
|
10
|
+
Translate.method_pre(:method?).should == :method_pre?
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should give the pre method for an exclamation method' do
|
14
|
+
Translate.method_pre(:method!).should == :method_pre!
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should give the pre method for an assignment method' do
|
18
|
+
Translate.method_pre(:method=).should == :method_pre=
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should give the pre method for an operator' do
|
22
|
+
Translate.method_pre(:+).should == :op_plus_pre
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should give the post method for a normal method' do
|
26
|
+
Translate.method_post(:method).should == :method_post
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should give the post method for a question method' do
|
30
|
+
Translate.method_post(:method?).should == :method_post?
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should give the post method for an exclamation method' do
|
34
|
+
Translate.method_post(:method!).should == :method_post!
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should give the post method for an assignment method' do
|
38
|
+
Translate.method_post(:method=).should == :method_post=
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should give the post method for an operator' do
|
42
|
+
Translate.method_post(:+).should == :op_plus_post
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: armin-joellenbeck-rdbc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Armin Joellenbeck
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-08-13 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: ""
|
17
|
+
email: armin@joellenbeck.net
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
files:
|
25
|
+
- README
|
26
|
+
- Rakefile
|
27
|
+
- rdbc.gemspec
|
28
|
+
- examples/stack_contract.rb
|
29
|
+
- examples/stack.rb
|
30
|
+
- lib/contract.rb
|
31
|
+
- lib/translate.rb
|
32
|
+
- lib/object.rb
|
33
|
+
- lib/proxy.rb
|
34
|
+
- spec/translate_spec.rb
|
35
|
+
- spec/object_spec.rb
|
36
|
+
- spec/proxy_spec.rb
|
37
|
+
- spec/spec.opts
|
38
|
+
- spec/contract_spec.rb
|
39
|
+
- spec/stack_contract_spec.rb
|
40
|
+
- spec/stack_spec.rb
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://github.com/armin-joellenbeck/rdbc/tree/master
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options:
|
45
|
+
- --all
|
46
|
+
- --charset
|
47
|
+
- utf8
|
48
|
+
- --main
|
49
|
+
- README
|
50
|
+
- --title
|
51
|
+
- Design by Contract for Ruby
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 1.2.0
|
70
|
+
signing_key:
|
71
|
+
specification_version: 2
|
72
|
+
summary: Design by Contract for Ruby.
|
73
|
+
test_files: []
|
74
|
+
|