safer 0.1.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/.autotest +23 -0
- data/History.txt +4 -0
- data/Manifest.txt +10 -0
- data/README.txt +69 -0
- data/Rakefile +8 -0
- data/lib/safer/ivar.rb +127 -0
- data/lib/safer/protocol.rb +335 -0
- data/lib/safer.rb +8 -0
- data/test/test_safer_ivar.rb +85 -0
- data/test/test_safer_protocol.rb +144 -0
- metadata +122 -0
data/.autotest
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'autotest/restart'
|
4
|
+
|
5
|
+
# Autotest.add_hook :initialize do |at|
|
6
|
+
# at.extra_files << "../some/external/dependency.rb"
|
7
|
+
#
|
8
|
+
# at.libs << ":../some/external"
|
9
|
+
#
|
10
|
+
# at.add_exception 'vendor'
|
11
|
+
#
|
12
|
+
# at.add_mapping(/dependency.rb/) do |f, _|
|
13
|
+
# at.files_matching(/test_.*rb$/)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# %w(TestA TestB).each do |klass|
|
17
|
+
# at.extra_class_map[klass] = "test/test_misc.rb"
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
|
21
|
+
# Autotest.add_hook :run_command do |at|
|
22
|
+
# system "rake build"
|
23
|
+
# end
|
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
= safer
|
2
|
+
|
3
|
+
http://safer.rubyforge.org/
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Safer is an umbrella library, with components designed to make it simple to
|
8
|
+
verify and improve the safety of your ruby code. There are at present two
|
9
|
+
modules under the safer umbrella:
|
10
|
+
|
11
|
+
[<tt>Safer::IVar</tt>] generates specially-named accessor functions
|
12
|
+
for class instance variables.
|
13
|
+
[<tt>Safer::ClassProtocol</tt>] is used to provide a ruby analogue to
|
14
|
+
Objective-C Protocols (which are similar to
|
15
|
+
Java interfaces, but do not require
|
16
|
+
inheritance).
|
17
|
+
|
18
|
+
== FEATURES/PROBLEMS:
|
19
|
+
|
20
|
+
* Name instance variables after the class in which they are defined. Generate
|
21
|
+
accessor functions for these instance variables.
|
22
|
+
* Define an "Protocol" class, and derive a signature from it. Verify that
|
23
|
+
another class (or an instance of that class) implements the protocol
|
24
|
+
signature.
|
25
|
+
|
26
|
+
== SYNOPSIS:
|
27
|
+
|
28
|
+
require 'safer/ivar'
|
29
|
+
require 'safer/protocol'
|
30
|
+
|
31
|
+
module MyModule
|
32
|
+
class MyProtocolClass
|
33
|
+
def self.class_method_foo(arg1, arg2)
|
34
|
+
end
|
35
|
+
def instance_method(arg3)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
PROTOCOL = Safer::Protocol.create_from_class(MyProtocolClass)
|
39
|
+
class MyClass
|
40
|
+
Safer::IVar.instance_variable(self, :var1, :var2)
|
41
|
+
def initialize(var1, var2)
|
42
|
+
PROTOCOL.check_instance_conforms(var1)
|
43
|
+
PROTOCOL.check_instance_conforms(var2)
|
44
|
+
self.mymodule_myclass__var1 = var1
|
45
|
+
self.mymodule_myclass__var2 = var2
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
== REQUIREMENTS:
|
51
|
+
|
52
|
+
* None
|
53
|
+
|
54
|
+
== INSTALL:
|
55
|
+
|
56
|
+
* sudo gem install
|
57
|
+
|
58
|
+
== DEVELOPERS:
|
59
|
+
|
60
|
+
After checking out the source, run:
|
61
|
+
|
62
|
+
$ rake newb
|
63
|
+
|
64
|
+
This task will install any missing dependencies, run the tests/specs,
|
65
|
+
and generate the RDoc.
|
66
|
+
|
67
|
+
== LICENSE:
|
68
|
+
|
69
|
+
This software is placed into the public domain.
|
data/Rakefile
ADDED
data/lib/safer/ivar.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
module Safer
|
2
|
+
##
|
3
|
+
# Create accessor functions for instance variables, in which the accessor
|
4
|
+
# function is prefixed with the name of the class in which the instance
|
5
|
+
# variable is defined.
|
6
|
+
module IVar
|
7
|
+
|
8
|
+
##
|
9
|
+
# Given a class name, derive the prefix string for the accessor functions
|
10
|
+
# that instance_variable will create.
|
11
|
+
def self.class_symbol_prefix(klass)
|
12
|
+
klass.to_s.split('::').inject(nil) do |memo, el|
|
13
|
+
# reset the symbol name when an anonymous class is encountered.
|
14
|
+
if /^\#<.*>$/.match(el)
|
15
|
+
nil
|
16
|
+
elsif memo
|
17
|
+
memo + '_' + el.downcase
|
18
|
+
else
|
19
|
+
el.downcase
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# compute accessor routine symbol names, so that the names include the
|
26
|
+
# entire class namespace.
|
27
|
+
# [+klass+] Class object for which to generate a symbol name.
|
28
|
+
# [+nmlist+] Array of +Symbol+ objects.
|
29
|
+
# [_return_] Array of +Symbol+ objects generated from +klass+ and each
|
30
|
+
# element of +nmlist+.
|
31
|
+
# For example, given the following listing:
|
32
|
+
# class MyClass
|
33
|
+
# class SubClass
|
34
|
+
# SYMNM = Safer::IVar::symbol_names(self, :foo)
|
35
|
+
# puts(SYMNM)
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
# the following output would be produced:
|
39
|
+
# myclass_subclass__foo
|
40
|
+
def self.symbol_names(klass, nmlist)
|
41
|
+
kname = self.class_symbol_prefix(klass)
|
42
|
+
|
43
|
+
nmlist.map do |nm|
|
44
|
+
(kname + '__' + nm.to_s).to_sym
|
45
|
+
end
|
46
|
+
end # Safer::IVar::symbol_names
|
47
|
+
|
48
|
+
##
|
49
|
+
# create accessor routines for an instance variable, with variable name
|
50
|
+
# determined by the class in which the instance variable was declared.
|
51
|
+
# [+klass+] Class object into which to generate the variable accessor
|
52
|
+
# functions.
|
53
|
+
# [+nmlist+] List of symbols for which to create accessor routines.
|
54
|
+
# Uses Safer::IVar::symbol_names to determine the symbol names of the
|
55
|
+
# accessor routines.
|
56
|
+
# For example, the listing:
|
57
|
+
# class MyClass
|
58
|
+
# class SubClass
|
59
|
+
# Safer::IVar::instance_variable(self, :foo, :bar)
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
# is equivalent to the following code:
|
63
|
+
# class MyClass
|
64
|
+
# class SubClass
|
65
|
+
# attr_accessor :myclass_subclass__foo
|
66
|
+
# attr_accessor :myclass_subclass__bar
|
67
|
+
# end
|
68
|
+
# end
|
69
|
+
# The name-mangling signals that these instance variables are, for all
|
70
|
+
# intents and purposes, private to +klass+.
|
71
|
+
def self.instance_variable(klass, *nmlist)
|
72
|
+
self.symbol_names(klass, nmlist).each do |symnm|
|
73
|
+
klass.class_eval do
|
74
|
+
attr_accessor symnm
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end # Safer::IVar::instance_variable
|
78
|
+
|
79
|
+
##
|
80
|
+
# export the reader routines for instance variables in a nicer way.
|
81
|
+
# [+klass+] Class object for which to define reader accessors for
|
82
|
+
# instance variables defined by Safer::IVar::instance_variables.
|
83
|
+
# [+nmlist+] List of symbols for which to define reader accessors.
|
84
|
+
# Each symbol in +nmlist+ should have previously been given as
|
85
|
+
# an argument to Safer::IVar::instance_variables(+klass+).
|
86
|
+
def self.export_reader(klass, *nmlist)
|
87
|
+
symlist = self.symbol_names(klass, nmlist)
|
88
|
+
nmlist.size.times do |index|
|
89
|
+
thisnm = nmlist[index]
|
90
|
+
thissym = symlist[index]
|
91
|
+
klass.class_eval("def #{thisnm} ; self.#{thissym} ; end")
|
92
|
+
end
|
93
|
+
end # Safer::IVar::export_reader
|
94
|
+
|
95
|
+
##
|
96
|
+
# export the writer routines for instance variables in a nicer way.
|
97
|
+
# [+klass+] Class object for which to define writer accessors for
|
98
|
+
# instance variables defined by Safer::IVar::instance_variables.
|
99
|
+
# [+nmlist+] List of symbols for which to define writer accessors.
|
100
|
+
# Each symbol in +nmlist+ should have previously been given as
|
101
|
+
# an argument to Safer::IVar::instance_variables(+klass+).
|
102
|
+
def self.export_writer(klass, *nmlist)
|
103
|
+
symlist = self.symbol_names(klass, nmlist)
|
104
|
+
nmlist.size.times do |index|
|
105
|
+
thisnm = nmlist[index]
|
106
|
+
thissym = symlist[index]
|
107
|
+
klass.class_eval(
|
108
|
+
"def #{thisnm}=(value) ; self.#{thissym} = value ; end"
|
109
|
+
)
|
110
|
+
end
|
111
|
+
end # Safer::IVar::export_writer
|
112
|
+
|
113
|
+
##
|
114
|
+
# export both reader and writer routines for instance variables in a
|
115
|
+
# nicer way.
|
116
|
+
# [+klass+] Class object for which to define accessors for
|
117
|
+
# instance variables defined by Safer::IVar::instance_variables.
|
118
|
+
# [+nmlist+] List of symbols for which to define accessors.
|
119
|
+
# Each symbol in +nmlist+ should have previously been given as
|
120
|
+
# an argument to Safer::IVar::instance_variables(+klass+).
|
121
|
+
def self.export_accessor(klass, *nmlist)
|
122
|
+
self.export_reader(klass, *nmlist)
|
123
|
+
self.export_writer(klass, *nmlist)
|
124
|
+
end # Safer::IVar::export_accessor
|
125
|
+
|
126
|
+
end # Safer::IVar
|
127
|
+
end # Safer
|
@@ -0,0 +1,335 @@
|
|
1
|
+
require 'safer/ivar'
|
2
|
+
|
3
|
+
module Safer
|
4
|
+
## Safer::ClassProtocol
|
5
|
+
# Named set of class- and instance- methods, with associated numbers of
|
6
|
+
# arguments. Call +create_from_class+ or +create_from_instance+ to create a
|
7
|
+
# new +ClassProtocol+ object. Call +class_conforms?+ or +instance_conforms?+
|
8
|
+
# to verify that a Class or instance of the class (respectively) conforms to
|
9
|
+
# a protocol.
|
10
|
+
class ClassProtocol
|
11
|
+
|
12
|
+
##
|
13
|
+
# Utility function used during ClassProtocol operations. Given an
|
14
|
+
# +Array+, construct a +Hash+ with the values in the +Array+ as keys.
|
15
|
+
# If no block is provided, then the values for all keys in the +Hash+ will
|
16
|
+
# be +true+. If a block is provided, the block is called for each value
|
17
|
+
# in the +Array+, and its return value will be stored as the value for
|
18
|
+
# that element in the +Hash+. If the block returns +nil+, then the element
|
19
|
+
# will not be stored in the resulting +Hash+.
|
20
|
+
def self._array_to_table(array, &block)
|
21
|
+
if ! block
|
22
|
+
block = proc do |el| true ; end
|
23
|
+
end
|
24
|
+
array.inject(Hash::new) do |h, el|
|
25
|
+
newval = block.call(h, el)
|
26
|
+
if newval
|
27
|
+
h[el] = newval
|
28
|
+
end
|
29
|
+
h
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Describes a set of methods that should be implemented by an object, but
|
35
|
+
# are not implemented. The ClassProtocol::Error object will contain two
|
36
|
+
# ClassProtocol::Violation objects: one for class-method protocol
|
37
|
+
# violations, and one for instance-method protocol violations.
|
38
|
+
class Violation
|
39
|
+
Safer::IVar::instance_variable(self, :table)
|
40
|
+
Safer::IVar::instance_variable(self, :report)
|
41
|
+
|
42
|
+
##
|
43
|
+
# :attr_reader: table
|
44
|
+
# Hash describing method violations. Keys are the names of methods
|
45
|
+
# containing errors. If a method was unimplemented in the class, the
|
46
|
+
# value for that method will be +true+. If a method was implemented, but
|
47
|
+
# had a different arity (number of required / optional arguments) than
|
48
|
+
# the protocol, the value for that method will be the arity discovered in
|
49
|
+
# the object. (The desired arity can be accessed through the
|
50
|
+
# ClassProtocol::Signature for that method.)
|
51
|
+
Safer::IVar::export_reader(self, :table)
|
52
|
+
##
|
53
|
+
# :attr_reader: report
|
54
|
+
# String describing the errors from +table+ in a more human-readable
|
55
|
+
# fashion.
|
56
|
+
Safer::IVar::export_reader(self, :report)
|
57
|
+
|
58
|
+
##
|
59
|
+
# Create a new ClassProtocol::Violation object.
|
60
|
+
def initialize(table, report)
|
61
|
+
self.safer_classprotocol_violation__table = table
|
62
|
+
self.safer_classprotocol_violation__report = report
|
63
|
+
end
|
64
|
+
##
|
65
|
+
# Compare two ClassProtocol::Violation objects for content equality.
|
66
|
+
def ==(other_object)
|
67
|
+
self.class == other_object.class &&
|
68
|
+
self.safer_classprotocol_violation__table == other_object.safer_classprotocol_violation__table
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Hash of method_name => method.arity, describing a set of methods that
|
74
|
+
# we expect to see implemented in another class. ClassProtocol will contain
|
75
|
+
# two of these objects - one describing class methods, and one describing
|
76
|
+
# instance methods.
|
77
|
+
class Signature
|
78
|
+
Safer::IVar::instance_variable(self, :table)
|
79
|
+
##
|
80
|
+
# :attr_reader: table
|
81
|
+
# +Hash+ object, in which the keys are the names of methods, and the
|
82
|
+
# value for a key is the required arity of that method.
|
83
|
+
Safer::IVar::export_reader(self, :table)
|
84
|
+
|
85
|
+
##
|
86
|
+
# Create a ClassProtocol::Signature object.
|
87
|
+
def initialize(table)
|
88
|
+
self.safer_classprotocol_signature__table = table
|
89
|
+
end
|
90
|
+
##
|
91
|
+
# Compare two ClassProtocol::Signature objects for content equality.
|
92
|
+
def ==(other_object)
|
93
|
+
self.class == other_object.class &&
|
94
|
+
self.safer_classprotocol_signature__table == other_object.safer_classprotocol_signature__table
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Given an object, and the name of the method for getting a named method
|
99
|
+
# from that object, check that the object implements this function
|
100
|
+
# signature.
|
101
|
+
def find_violations(object, get_method)
|
102
|
+
report = ''
|
103
|
+
have_error = false
|
104
|
+
error_table = Hash::new
|
105
|
+
self.safer_classprotocol_signature__table.each_pair do |name, arity|
|
106
|
+
begin
|
107
|
+
m = object.send(get_method, name)
|
108
|
+
if m.arity != arity
|
109
|
+
report += "#{get_method}(#{name}) arity #{m.arity} != desired #{arity}.\n"
|
110
|
+
error_table[name] = m.arity
|
111
|
+
have_error = true
|
112
|
+
end
|
113
|
+
rescue NameError => e
|
114
|
+
report += "#{get_method}(#{name}) unimplemented.\n"
|
115
|
+
error_table[name] = true
|
116
|
+
have_error = true
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
if have_error
|
121
|
+
Safer::ClassProtocol::Violation.new(error_table, report)
|
122
|
+
else
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Derive a Signature from an object.
|
129
|
+
def self.create(object, get_methods, get_method, &filter)
|
130
|
+
method_names = object.send(get_methods)
|
131
|
+
method_table = Safer::ClassProtocol._array_to_table(method_names) do
|
132
|
+
|h, method_name|
|
133
|
+
if filter.call(method_name)
|
134
|
+
m = object.send(get_method, method_name)
|
135
|
+
m.arity
|
136
|
+
end
|
137
|
+
end
|
138
|
+
self.new(method_table)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
## Safer::ClassProtocol::Error
|
143
|
+
# Error generated when a class does not conform to a protocol signature.
|
144
|
+
class Error < StandardError
|
145
|
+
Safer::IVar::instance_variable(self, :error_object)
|
146
|
+
Safer::IVar::instance_variable(self, :protocol)
|
147
|
+
Safer::IVar::instance_variable(self, :class_violations)
|
148
|
+
Safer::IVar::instance_variable(self, :instance_violations)
|
149
|
+
Safer::IVar::instance_variable(self, :report)
|
150
|
+
|
151
|
+
##
|
152
|
+
# :attr_reader: error_object
|
153
|
+
# Object that does not properly implement a desired method protocol.
|
154
|
+
Safer::IVar::export_reader(self, :error_object)
|
155
|
+
##
|
156
|
+
# :attr_reader: protocol
|
157
|
+
# ClassProtocol object describing the signature that error_object failed
|
158
|
+
# to properly implement.
|
159
|
+
Safer::IVar::export_reader(self, :protocol)
|
160
|
+
##
|
161
|
+
# :attr_reader: class_violations
|
162
|
+
# ClassProtocol::Violation describing class-method protocol violations.
|
163
|
+
Safer::IVar::export_reader(self, :class_violations)
|
164
|
+
##
|
165
|
+
# :attr_reader: instance_violations
|
166
|
+
# ClassProtocol::Violation describing instance-method protocol violations.
|
167
|
+
Safer::IVar::export_reader(self, :instance_violations)
|
168
|
+
##
|
169
|
+
# :attr_reader: report
|
170
|
+
# String for displaying human-readable error message describing protocol
|
171
|
+
# violations.
|
172
|
+
Safer::IVar::export_reader(self, :report)
|
173
|
+
|
174
|
+
|
175
|
+
##
|
176
|
+
# Create a Safer::ClassProtocol::Error object.
|
177
|
+
# Used internally.
|
178
|
+
def initialize(message, error_object, protocol, class_violations, instance_violations)
|
179
|
+
super(message)
|
180
|
+
self.safer_classprotocol_error__error_object = error_object
|
181
|
+
self.safer_classprotocol_error__protocol = protocol
|
182
|
+
self.safer_classprotocol_error__class_violations = class_violations
|
183
|
+
self.safer_classprotocol_error__instance_violations = instance_violations
|
184
|
+
report = ''
|
185
|
+
if class_violations
|
186
|
+
report +=
|
187
|
+
"Class method errors:\n" +
|
188
|
+
self.safer_classprotocol_error__class_violations.report
|
189
|
+
if instance_violations
|
190
|
+
report += "\n\n"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
if instance_violations
|
194
|
+
report +=
|
195
|
+
"Instance method errors:\n" +
|
196
|
+
self.safer_classprotocol_error__instance_violations.report
|
197
|
+
end
|
198
|
+
self.safer_classprotocol_error__report = report
|
199
|
+
end # Safer::ClassProtocol::Error#initialize
|
200
|
+
|
201
|
+
##
|
202
|
+
# Compare two Safer::ClassProtocol::Error objects for content equality.
|
203
|
+
def ==(other_object)
|
204
|
+
self.class == other_object.class &&
|
205
|
+
self.error_object == other_object.error_object &&
|
206
|
+
self.protocol == other_object.protocol &&
|
207
|
+
self.class_violations == other_object.class_violations &&
|
208
|
+
self.instance_violations == other_object.instance_violations
|
209
|
+
end
|
210
|
+
|
211
|
+
##
|
212
|
+
# Error generated when a Class object does not conform to a desired
|
213
|
+
# protocol.
|
214
|
+
class ClassError < Safer::ClassProtocol::Error
|
215
|
+
##
|
216
|
+
# Create a Safer::ClassProtocol::Error::ClassError object.
|
217
|
+
def initialize(error_object, protocol, class_violations, instance_violations)
|
218
|
+
super(
|
219
|
+
"Class #{error_object} does not implement protocol" +
|
220
|
+
"#{protocol.name}.",
|
221
|
+
error_object, protocol, class_violations, instance_violations)
|
222
|
+
end
|
223
|
+
end # Safer::ClassProtocol::Error::ClassError
|
224
|
+
|
225
|
+
##
|
226
|
+
# Error generated when an instance object does not conform to a desired
|
227
|
+
# protocol.
|
228
|
+
class InstanceError < Safer::ClassProtocol::Error
|
229
|
+
##
|
230
|
+
# Create a Safer::ClassProtocol::Error::InstanceError object.
|
231
|
+
def initialize(error_object, protocol, class_violations, instance_violations)
|
232
|
+
super(
|
233
|
+
"Instance of #{error_object.class} does not implement protocol" +
|
234
|
+
"#{protocol.name}.",
|
235
|
+
error_object, protocol, class_violations, instance_violations)
|
236
|
+
end
|
237
|
+
end # Safer::ClassProtocol::Error::InstanceError
|
238
|
+
end # Safer::ClassProtocol::Error
|
239
|
+
|
240
|
+
|
241
|
+
Safer::IVar::instance_variable(self, :name)
|
242
|
+
Safer::IVar::instance_variable(self, :class_signature)
|
243
|
+
Safer::IVar::instance_variable(self, :instance_signature)
|
244
|
+
|
245
|
+
##
|
246
|
+
# :attr_reader: name
|
247
|
+
# Name of this protocol signature. Typically derived from the class name
|
248
|
+
# from which this signature is derived.
|
249
|
+
Safer::IVar::export_reader(self, :name)
|
250
|
+
##
|
251
|
+
# :attr_reader: class_signature
|
252
|
+
# Signatures of required class methods for objects implementing this
|
253
|
+
# protocol.
|
254
|
+
Safer::IVar::export_reader(self, :class_signature)
|
255
|
+
##
|
256
|
+
# :attr_reader: instance_signature
|
257
|
+
# Signatures of required instance methods for objects implementing this
|
258
|
+
# protocol.
|
259
|
+
Safer::IVar::export_reader(self, :instance_signature)
|
260
|
+
|
261
|
+
##
|
262
|
+
# Create a +ClassProtocol+ object.
|
263
|
+
# Used internally.
|
264
|
+
# [+name+] Value for +name+ field object.
|
265
|
+
# [+class_signature+] Value for +class_signature+ field.
|
266
|
+
# [+instance_signature+] Value for +instance_signature+ field.
|
267
|
+
def initialize(name, class_signature, instance_signature)
|
268
|
+
self.safer_classprotocol__name = name
|
269
|
+
self.safer_classprotocol__class_signature = class_signature
|
270
|
+
self.safer_classprotocol__instance_signature = instance_signature
|
271
|
+
end # Safer::ClassProtocol#initialize
|
272
|
+
|
273
|
+
##
|
274
|
+
# Check that a Class object conforms to this protocol.
|
275
|
+
def class_conforms?(klass)
|
276
|
+
class_violations = self.safer_classprotocol__class_signature.find_violations(klass, :method)
|
277
|
+
instance_violations = self.safer_classprotocol__instance_signature.find_violations(klass, :instance_method)
|
278
|
+
if class_violations || instance_violations
|
279
|
+
raise(Error::ClassError.new(klass, self, class_violations, instance_violations))
|
280
|
+
end
|
281
|
+
true
|
282
|
+
end # Safer::ClassProtocol#class_conforms?
|
283
|
+
##
|
284
|
+
# Check that an instance object conforms to this protocol.
|
285
|
+
def instance_conforms?(instance)
|
286
|
+
class_violations = self.safer_classprotocol__class_signature.find_violations(instance.class, :method)
|
287
|
+
instance_violations = self.safer_classprotocol__instance_signature.find_violations(instance, :method)
|
288
|
+
if class_violations || instance_violations
|
289
|
+
raise(Error::InstanceError.new(instance, self, class_violations, instance_violations))
|
290
|
+
end
|
291
|
+
true
|
292
|
+
end # Safer::ClassProtocol#instance_conforms?
|
293
|
+
|
294
|
+
##
|
295
|
+
# Given a +Class+ object +klass+ and a +Class+ object +base+, create a
|
296
|
+
# +ClassProtocol+ object representing the methods implemented in +klass+
|
297
|
+
# that are not present in +base+.
|
298
|
+
# [+klass+] +Class+ object from which to derive a +ClassProtocol+.
|
299
|
+
# [+base+] +Class+ object describing routines that will be implemented by
|
300
|
+
# +klass+, but which should not be included in the returned
|
301
|
+
# +ClassProtocol+.
|
302
|
+
def self.create_from_class(klass, base = Object)
|
303
|
+
class_signature = Signature.create(klass, :methods, :method) do
|
304
|
+
|method_name|
|
305
|
+
! Class.respond_to?(method_name)
|
306
|
+
end
|
307
|
+
baseinstance_table = self._array_to_table(base.instance_methods)
|
308
|
+
instance_signature = Signature.create(klass, :instance_methods, :instance_method) do
|
309
|
+
|method_name|
|
310
|
+
! baseinstance_table.has_key?(method_name)
|
311
|
+
end
|
312
|
+
|
313
|
+
self.new(klass.to_s, class_signature, instance_signature)
|
314
|
+
end # Safer::ClassProtocol.create_from_class
|
315
|
+
|
316
|
+
##
|
317
|
+
# Given an instance object +instance+ and a +Class+ object +baseclass+,
|
318
|
+
# create a +ClassProtocol+ object representing the methods implemented in
|
319
|
+
# +klass+ that are not present in +base+.
|
320
|
+
def self.create_from_instance(instance, baseclass = Object)
|
321
|
+
class_signature = Signature.create(instance.class, :methods, :method) do
|
322
|
+
|method_name|
|
323
|
+
! Class.method_defined?(method_name)
|
324
|
+
end
|
325
|
+
baseinstance_table = self._array_to_table(base.instance_methods)
|
326
|
+
instance_signature = Signature.create(instance, :methods, :method) do
|
327
|
+
|method_name|
|
328
|
+
! baseinstance_table.has_key?(method_name)
|
329
|
+
end
|
330
|
+
|
331
|
+
self.new(instance.class.to_s, class_signature, instance_signature)
|
332
|
+
end # Safer::ClassProtocol.create_from_instance
|
333
|
+
|
334
|
+
end # Safer::ClassProtocol
|
335
|
+
end # Safer
|
data/lib/safer.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'safer/ivar'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
module Root
|
5
|
+
class Test
|
6
|
+
class Instance
|
7
|
+
Safer::IVar::instance_variable(self, :instance)
|
8
|
+
end
|
9
|
+
class Reader
|
10
|
+
Safer::IVar::instance_variable(self, :instance)
|
11
|
+
Safer::IVar::export_reader(self, :instance)
|
12
|
+
end
|
13
|
+
class Writer
|
14
|
+
Safer::IVar::instance_variable(self, :instance)
|
15
|
+
Safer::IVar::export_writer(self, :instance)
|
16
|
+
end
|
17
|
+
class Accessor
|
18
|
+
Safer::IVar::instance_variable(self, :instance)
|
19
|
+
Safer::IVar::export_accessor(self, :instance)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class TC_SaferIVar < Test::Unit::TestCase
|
25
|
+
def setup
|
26
|
+
end
|
27
|
+
def teardown
|
28
|
+
end
|
29
|
+
|
30
|
+
def assert_no_respond_to(object, method, message="")
|
31
|
+
_wrap_assertion do
|
32
|
+
full_message =
|
33
|
+
build_message(
|
34
|
+
nil,
|
35
|
+
"<?>\ngiven as the method name argument to #assert_no_respond_to " +
|
36
|
+
"must be a Symbol or #respond_to\\?(:to_str).",
|
37
|
+
method
|
38
|
+
)
|
39
|
+
|
40
|
+
assert_block(full_message) do
|
41
|
+
method.kind_of?(Symbol) || method.respond_to?(:to_str)
|
42
|
+
end
|
43
|
+
full_message =
|
44
|
+
build_message(
|
45
|
+
message, "<?>\nof type <?>\nnot expected to respond_to\\\\?<?>.\n",
|
46
|
+
object, object.class, method
|
47
|
+
)
|
48
|
+
assert_block(full_message) { not object.respond_to?(method) }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_class_symbol_prefix
|
53
|
+
assert_equal("root", Safer::IVar::class_symbol_prefix(Root))
|
54
|
+
assert_equal("root_test", Safer::IVar::class_symbol_prefix(Root::Test))
|
55
|
+
assert_equal("root_test_instance", Safer::IVar::class_symbol_prefix(Root::Test::Instance))
|
56
|
+
k = Root.class_eval("$k = Class::new(Root::Test::Instance)")
|
57
|
+
assert_same(nil, Safer::IVar::class_symbol_prefix(k))
|
58
|
+
b = k.class_eval("class Blah ; end ; Blah")
|
59
|
+
assert_equal("blah", Safer::IVar::class_symbol_prefix(b))
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_instance
|
63
|
+
test_accessors = proc do |obj, expectRead, expectWrite|
|
64
|
+
if expectRead
|
65
|
+
assert_respond_to(obj, :instance)
|
66
|
+
else
|
67
|
+
assert_no_respond_to(obj, :instance)
|
68
|
+
end
|
69
|
+
if expectWrite
|
70
|
+
assert_respond_to(obj, :instance=)
|
71
|
+
else
|
72
|
+
assert_no_respond_to(obj, :instance=)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
ti = Root::Test::Instance::new
|
76
|
+
assert_respond_to(ti, :root_test_instance__instance)
|
77
|
+
test_accessors.call(ti, false, false)
|
78
|
+
tr = Root::Test::Reader::new
|
79
|
+
test_accessors.call(tr, true, false)
|
80
|
+
tw = Root::Test::Writer::new
|
81
|
+
test_accessors.call(tw, false, true)
|
82
|
+
ta = Root::Test::Accessor::new
|
83
|
+
test_accessors.call(ta, true, true)
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'safer/protocol'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
module ArityModule
|
5
|
+
def arity_empty
|
6
|
+
end
|
7
|
+
def arity_1(one)
|
8
|
+
end
|
9
|
+
def arity_2(one, two)
|
10
|
+
end
|
11
|
+
def arity_any(*args)
|
12
|
+
end
|
13
|
+
def arity_2_any(one, two, *args)
|
14
|
+
end
|
15
|
+
METHODS = %w(empty 1 2 any 2_any).map do |suffix| "arity_#{suffix}".to_sym end
|
16
|
+
end
|
17
|
+
|
18
|
+
class TestArityClass
|
19
|
+
extend ArityModule
|
20
|
+
end
|
21
|
+
class TestArityInstance
|
22
|
+
include ArityModule
|
23
|
+
end
|
24
|
+
class TestArityBoth
|
25
|
+
extend ArityModule
|
26
|
+
include ArityModule
|
27
|
+
end
|
28
|
+
|
29
|
+
class TestArityError
|
30
|
+
include ArityModule
|
31
|
+
def arity_empty(blah)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class TC_SaferProtocol < Test::Unit::TestCase
|
36
|
+
def setup
|
37
|
+
@class_protocol = Safer::ClassProtocol.create_from_class(TestArityClass)
|
38
|
+
@instance_protocol = Safer::ClassProtocol.create_from_class(TestArityInstance)
|
39
|
+
@both_protocol = Safer::ClassProtocol.create_from_class(TestArityBoth)
|
40
|
+
|
41
|
+
@class_instance = TestArityClass::new
|
42
|
+
@instance_instance = TestArityInstance::new
|
43
|
+
@both_instance = TestArityBoth::new
|
44
|
+
@error_instance = TestArityError::new
|
45
|
+
end
|
46
|
+
def teardown
|
47
|
+
@class_protocol = nil
|
48
|
+
@instance_protocol = nil
|
49
|
+
@both_protocol = nil
|
50
|
+
|
51
|
+
@class_instance = nil
|
52
|
+
@instance_instance = nil
|
53
|
+
@both_instance = nil
|
54
|
+
@error_instance = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_methods(useinstance)
|
58
|
+
if useinstance
|
59
|
+
[ :instance_conforms?, proc do |obj| obj ; end, Safer::ClassProtocol::Error::InstanceError ]
|
60
|
+
else
|
61
|
+
[ :class_conforms?, proc do |obj| obj.class ; end, Safer::ClassProtocol::Error::ClassError ]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_signaturesCorrect
|
66
|
+
assert_equal(@both_protocol.class_signature, @class_protocol.class_signature)
|
67
|
+
assert_equal(@both_protocol.instance_signature, @instance_protocol.instance_signature)
|
68
|
+
assert_equal(@class_protocol.class_signature, @instance_protocol.instance_signature)
|
69
|
+
arity_methods = ArityModule::METHODS.map do |meth| meth.to_s end.sort
|
70
|
+
assert_equal(@class_protocol.class_signature.table.keys.sort, arity_methods)
|
71
|
+
end
|
72
|
+
|
73
|
+
def run_shouldConform(useinstance)
|
74
|
+
conforms_method, object_get, except_type = get_methods(useinstance)
|
75
|
+
assert_nothing_thrown do
|
76
|
+
@class_protocol.send(conforms_method, object_get.call(@class_instance))
|
77
|
+
@instance_protocol.send(conforms_method, object_get.call(@instance_instance))
|
78
|
+
@both_protocol.send(conforms_method, object_get.call(@both_instance))
|
79
|
+
@class_protocol.send(conforms_method, object_get.call(@both_instance))
|
80
|
+
@instance_protocol.send(conforms_method, object_get.call(@both_instance))
|
81
|
+
end
|
82
|
+
end
|
83
|
+
def test_shouldConform
|
84
|
+
run_shouldConform(false)
|
85
|
+
run_shouldConform(true)
|
86
|
+
end
|
87
|
+
|
88
|
+
def run_arity(useinstance)
|
89
|
+
conforms_method, object_get, except_type = get_methods(useinstance)
|
90
|
+
begin
|
91
|
+
@instance_protocol.send(conforms_method, object_get.call(@error_instance))
|
92
|
+
flunk("Expected exception.")
|
93
|
+
rescue Safer::ClassProtocol::Error => e
|
94
|
+
assert_same(e.class, except_type)
|
95
|
+
assert_same(object_get.call(@error_instance), e.error_object)
|
96
|
+
assert_same(@instance_protocol, e.protocol)
|
97
|
+
assert_same(nil, e.class_violations)
|
98
|
+
assert_equal({ 'arity_empty' => 1 }, e.instance_violations.table)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
def test_arity
|
102
|
+
run_arity(false)
|
103
|
+
run_arity(true)
|
104
|
+
end
|
105
|
+
|
106
|
+
def run_classInstanceError(useinstance)
|
107
|
+
conforms_method, object_get, except_type = get_methods(useinstance)
|
108
|
+
|
109
|
+
class_instance_error = nil
|
110
|
+
begin
|
111
|
+
@instance_protocol.send(conforms_method, object_get.call(@class_instance))
|
112
|
+
flunk("Expected exception.")
|
113
|
+
rescue Safer::ClassProtocol::Error => e
|
114
|
+
assert_same(e.class, except_type)
|
115
|
+
assert_same(e.error_object, object_get.call(@class_instance))
|
116
|
+
assert_same(e.protocol, @instance_protocol)
|
117
|
+
assert_same(nil, e.class_violations)
|
118
|
+
assert_equal(e.instance_violations.table.keys.size, @instance_protocol.instance_signature.table.keys.size)
|
119
|
+
@instance_protocol.instance_signature.table.each_pair do |key, arity|
|
120
|
+
assert(e.instance_violations.table.has_key?(key))
|
121
|
+
assert_same(e.instance_violations.table[key], true)
|
122
|
+
end
|
123
|
+
class_instance_error = e
|
124
|
+
end
|
125
|
+
|
126
|
+
begin
|
127
|
+
@both_protocol.send(conforms_method, object_get.call(@class_instance))
|
128
|
+
flunk("Expected exception.")
|
129
|
+
rescue Safer::ClassProtocol::Error => e
|
130
|
+
assert_same(e.class, except_type)
|
131
|
+
assert_same(e.error_object, object_get.call(@class_instance))
|
132
|
+
assert_same(e.protocol, @both_protocol)
|
133
|
+
assert_equal(
|
134
|
+
e.instance_violations, class_instance_error.instance_violations)
|
135
|
+
assert_same(
|
136
|
+
e.class_violations, class_instance_error.class_violations)
|
137
|
+
assert_equal(e.report, class_instance_error.report)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
def test_classInstanceError
|
141
|
+
run_classInstanceError(false)
|
142
|
+
run_classInstanceError(true)
|
143
|
+
end
|
144
|
+
end
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: safer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Aidan Cully
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-09-23 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rubyforge
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 7
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 0
|
33
|
+
- 4
|
34
|
+
version: 2.0.4
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: hoe
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 19
|
46
|
+
segments:
|
47
|
+
- 2
|
48
|
+
- 6
|
49
|
+
- 2
|
50
|
+
version: 2.6.2
|
51
|
+
type: :development
|
52
|
+
version_requirements: *id002
|
53
|
+
description: |-
|
54
|
+
Safer is an umbrella library, with components designed to make it simple to
|
55
|
+
verify and improve the safety of your ruby code. There are at present two
|
56
|
+
modules under the safer umbrella:
|
57
|
+
|
58
|
+
[<tt>Safer::IVar</tt>] generates specially-named accessor functions
|
59
|
+
for class instance variables.
|
60
|
+
[<tt>Safer::ClassProtocol</tt>] is used to provide a ruby analogue to
|
61
|
+
Objective-C Protocols (which are similar to
|
62
|
+
Java interfaces, but do not require
|
63
|
+
inheritance).
|
64
|
+
email:
|
65
|
+
- aidan@panix.com
|
66
|
+
executables: []
|
67
|
+
|
68
|
+
extensions: []
|
69
|
+
|
70
|
+
extra_rdoc_files:
|
71
|
+
- History.txt
|
72
|
+
- Manifest.txt
|
73
|
+
- README.txt
|
74
|
+
files:
|
75
|
+
- .autotest
|
76
|
+
- History.txt
|
77
|
+
- Manifest.txt
|
78
|
+
- README.txt
|
79
|
+
- Rakefile
|
80
|
+
- lib/safer.rb
|
81
|
+
- lib/safer/ivar.rb
|
82
|
+
- lib/safer/protocol.rb
|
83
|
+
- test/test_safer_ivar.rb
|
84
|
+
- test/test_safer_protocol.rb
|
85
|
+
has_rdoc: true
|
86
|
+
homepage: http://safer.rubyforge.org/
|
87
|
+
licenses: []
|
88
|
+
|
89
|
+
post_install_message:
|
90
|
+
rdoc_options:
|
91
|
+
- --main
|
92
|
+
- README.txt
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
hash: 3
|
101
|
+
segments:
|
102
|
+
- 0
|
103
|
+
version: "0"
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
hash: 3
|
110
|
+
segments:
|
111
|
+
- 0
|
112
|
+
version: "0"
|
113
|
+
requirements: []
|
114
|
+
|
115
|
+
rubyforge_project: aidancully
|
116
|
+
rubygems_version: 1.3.7
|
117
|
+
signing_key:
|
118
|
+
specification_version: 3
|
119
|
+
summary: Safer is an umbrella library, with components designed to make it simple to verify and improve the safety of your ruby code
|
120
|
+
test_files:
|
121
|
+
- test/test_safer_ivar.rb
|
122
|
+
- test/test_safer_protocol.rb
|