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