rubybreaker 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/ABOUT.md +20 -0
- data/NEWS +5 -0
- data/README.md +16 -352
- data/Rakefile +30 -16
- data/TOPICS.md +55 -0
- data/TUTORIAL.md +291 -0
- data/VERSION +1 -1
- data/bin/rubybreaker +32 -14
- data/lib/rubybreaker/runtime/monitor.rb +1 -1
- data/lib/rubybreaker/runtime.rb +41 -21
- data/lib/rubybreaker/task.rb +15 -9
- data/lib/rubybreaker/test/rspec.rb +3 -3
- data/lib/rubybreaker/test/testcase.rb +3 -3
- data/lib/rubybreaker.rb +31 -16
- data/test/integrated/{tc_both_broken_breakable.rb → tc_both_documented_and_undocumented.rb} +3 -4
- data/test/integrated/tc_class_methods.rb +1 -1
- data/test/integrated/tc_inherit_broken.rb +1 -1
- data/test/integrated/tc_method_missing.rb +1 -1
- data/test/integrated/tc_namespace.rb +1 -1
- data/test/integrated/tc_simple1.rb +1 -1
- data/test/testtask/tc_testtask.rb +2 -2
- data/test/ts_integrated.rb +1 -1
- data/test/ts_rspec.rb +1 -1
- data/webpage/about.html +50 -0
- data/webpage/footer.html +6 -1
- data/webpage/header.html +9 -3
- data/webpage/images/logo.png +0 -0
- data/webpage/images/title.png +0 -0
- data/webpage/index.html +31 -367
- data/webpage/rdoc/Object.html +3 -103
- data/webpage/rdoc/Rake/RubyBreakerTestTask.html +80 -18
- data/webpage/rdoc/Rake.html +3 -7
- data/webpage/rdoc/RubyBreaker/Breakable.html +4 -8
- data/webpage/rdoc/RubyBreaker/Broken.html +4 -8
- data/webpage/rdoc/RubyBreaker/Context.html +3 -7
- data/webpage/rdoc/RubyBreaker/Errors/InternalError.html +3 -7
- data/webpage/rdoc/RubyBreaker/Errors/InvalidSubtypeCheck.html +3 -7
- data/webpage/rdoc/RubyBreaker/Errors/InvalidTypeConstruction.html +3 -7
- data/webpage/rdoc/RubyBreaker/Errors/SubtypeFailure.html +3 -7
- data/webpage/rdoc/RubyBreaker/Errors/TypeError.html +3 -7
- data/webpage/rdoc/RubyBreaker/Errors/UserError.html +3 -7
- data/webpage/rdoc/RubyBreaker/Errors.html +3 -7
- data/webpage/rdoc/RubyBreaker/ObjectPosition.html +3 -7
- data/webpage/rdoc/RubyBreaker/Position.html +3 -7
- data/webpage/rdoc/RubyBreaker/RDocSupport.html +3 -7
- data/webpage/rdoc/RubyBreaker/RubyTypeUtils.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/Inspector.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/MethodInfo.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/Monitor.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/MonitorInstaller.html +10 -14
- data/webpage/rdoc/RubyBreaker/Runtime/MonitorSwitch.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/MonitorUtils.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/ObjectWrapper.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/Pluggable.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/TypeSigParser.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/TypeSigUnparser.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime/TypeSystem.html +3 -7
- data/webpage/rdoc/RubyBreaker/Runtime.html +42 -39
- data/webpage/rdoc/RubyBreaker/TypeComparer.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/AnyType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/BlockType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/DuckType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/FusionType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/MethodListType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/MethodType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/NilType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/NominalType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/OptionalType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/OrType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/SelfType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/Type.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs/VarLengthType.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeDefs.html +3 -7
- data/webpage/rdoc/RubyBreaker/TypeUnparser.html +3 -7
- data/webpage/rdoc/RubyBreaker/Typing.html +3 -7
- data/webpage/rdoc/RubyBreaker/Util.html +3 -7
- data/webpage/rdoc/RubyBreaker.html +48 -15
- data/webpage/rdoc/Test/Unit.html +3 -7
- data/webpage/rdoc/Test.html +3 -7
- data/webpage/rdoc/created.rid +18 -17
- data/webpage/rdoc/index.html +3 -7
- data/webpage/rdoc/js/search_index.js +1 -1
- data/webpage/rdoc/table_of_contents.html +28 -36
- data/webpage/rubybreaker.css +8 -6
- data/webpage/topics.html +85 -0
- data/webpage/tutorial.html +331 -0
- metadata +14 -8
- data/lib/rubybreaker/doc.rb +0 -3
- data/webpage/rdoc/Kernel.html +0 -286
- data/webpage/rdoc/Test/Unit/TestCase.html +0 -309
data/TUTORIAL.md
ADDED
@@ -0,0 +1,291 @@
|
|
1
|
+
# Tutorial
|
2
|
+
|
3
|
+
This tutorial will describe the basic usage of the tool, the RubyBreaker
|
4
|
+
Type Annotation Language, and the RubyBreaker Type System.
|
5
|
+
|
6
|
+
## Usage
|
7
|
+
|
8
|
+
RubyBreaker takes advantage of test cases that already come with the source
|
9
|
+
program. It is recommended that RubyBreaker is run as a Rake task, which
|
10
|
+
does require a minimum change in the Rakefile (but no code change in the
|
11
|
+
source program) but is better for a long-term maintenance. Regardless of the
|
12
|
+
mode you choose to run, no source code change is required.
|
13
|
+
|
14
|
+
Let us briefly see how RubyBreaker can be run directly as a command-line
|
15
|
+
program to understand the overall concept of the tool. We will explain how
|
16
|
+
to use RubyBreaker in a Rakefile later.
|
17
|
+
|
18
|
+
$ rubybreaker -v -s -l lib.rb -b A,B prog.rb
|
19
|
+
|
20
|
+
The above command runs RubyBreaker in verbose mode (`-v`) and will display
|
21
|
+
the output on the screen (`-s`). Before RubyBreaker runs `prog.rb`, it will
|
22
|
+
import (`-l`) `lib.rb` and instrument (`-b`) classes `A` and `B`.
|
23
|
+
Here is `lib.rb`:
|
24
|
+
|
25
|
+
class A
|
26
|
+
def foo(x)
|
27
|
+
x.to_s
|
28
|
+
end
|
29
|
+
end
|
30
|
+
class B
|
31
|
+
def bar(y,z)
|
32
|
+
y.foo(z)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
And, `prog.rb` simply imports the library file and executes it:
|
37
|
+
|
38
|
+
require "lib"
|
39
|
+
A.new.foo(1)
|
40
|
+
|
41
|
+
This example will show how `A#foo` method is given a type by RubyBreaker.
|
42
|
+
After running the command shown above, the following output will be
|
43
|
+
generated and displayed on the screen:
|
44
|
+
|
45
|
+
class A
|
46
|
+
typesig("foo(fixnum[to_s]) -> string")
|
47
|
+
end
|
48
|
+
|
49
|
+
Here, the `typesig` method call registers `foo` as a method type that takes
|
50
|
+
an object that has `Fixnum#to_s` method and returns a `String`. This
|
51
|
+
method is made available simply by importing `rubybreaker`. Now, assume
|
52
|
+
that an additional code, `B.new.bar(A.new,1)`, is added at the end of
|
53
|
+
`prog.rb`. The subsequent run will generate the following result:
|
54
|
+
|
55
|
+
class A
|
56
|
+
typesig("foo(fixnum[to_s]) -> string")
|
57
|
+
end
|
58
|
+
class B
|
59
|
+
typesig("bar(a[foo], fixnum[to_s]) -> string")
|
60
|
+
end
|
61
|
+
|
62
|
+
Keep in mind that RubyBreaker is designed to gather type information based
|
63
|
+
on the _actual_ execution of the source program. This means the program
|
64
|
+
should be equipped with test cases that have a reasonable program path
|
65
|
+
coverage. Additionally, RubyBreaker assumes that test runs are correct and
|
66
|
+
the program behaves correctly (for those test runs) as intended by the
|
67
|
+
programmer. This assumption is not a strong requirement, but is necessary to
|
68
|
+
obtain precise and accurate type information.
|
69
|
+
|
70
|
+
### Using Ruby Unit Testing Framework
|
71
|
+
|
72
|
+
Instead of manually inserting the entry point indicator in the source
|
73
|
+
program, you can take advantage of Ruby's built-in testing framework. This
|
74
|
+
is preferred to modifying the source program directly, especially for the
|
75
|
+
long term program maintainability. But no worries! This method is as simple
|
76
|
+
as the previous one.
|
77
|
+
|
78
|
+
require "test/unit"
|
79
|
+
require "rubybreaker" # This should come after test/unit.
|
80
|
+
class TestClassA < Test::Unit::TestCase
|
81
|
+
def setup()
|
82
|
+
RubyBreaker.break(Class1, Class2, ...)
|
83
|
+
...
|
84
|
+
end
|
85
|
+
# ...tests!...
|
86
|
+
end
|
87
|
+
|
88
|
+
That's it! The only requirements are to indicate to RubyBreaker which modules
|
89
|
+
and classes to "break" and to place `require rubybreaker` _after_
|
90
|
+
`require test/unit`.
|
91
|
+
|
92
|
+
### Using RSpec
|
93
|
+
|
94
|
+
The requirement is same for RSpec but use `before` instead of `setup` to
|
95
|
+
specify which modules and classes to "break".
|
96
|
+
|
97
|
+
require "rspec"
|
98
|
+
require "rubybreaker"
|
99
|
+
|
100
|
+
describe "TestClassA Test"
|
101
|
+
before { RubyBreaker.break(Class1, Class2, ...) }
|
102
|
+
...
|
103
|
+
# ...tests!...
|
104
|
+
end
|
105
|
+
|
106
|
+
### Using Rakefile
|
107
|
+
|
108
|
+
By running RubyBreaker along with the Rakefile, you can avoid modifying the
|
109
|
+
source program at all. (You no longer need to import `rubybreaker` in the
|
110
|
+
test cases neither.) Therefore, this is the recommended way to use
|
111
|
+
RubyBreaker. The following code snippet describes how it can be done:
|
112
|
+
|
113
|
+
require "rubybreaker/task"
|
114
|
+
...
|
115
|
+
desc "Run RubyBreaker"
|
116
|
+
Rake::RubyBreakerTestTask.new(:"rubybreaker") do |t|
|
117
|
+
t.libs << "lib"
|
118
|
+
t.test_files = ["test/foo/tc_foo1.rb"]
|
119
|
+
# ...Other test task options..
|
120
|
+
t.rubybreaker_opts << "-v" # run in verbose mode
|
121
|
+
t.break = ["Class1", "Class2", ...] # specify what to monitor
|
122
|
+
end
|
123
|
+
|
124
|
+
Note that `RubyBrakerTestTask` can simply replace your `TestTask` block in
|
125
|
+
Rakefile. In fact, the former is a subclass of the latter and includes all
|
126
|
+
features supported by the latter. The only additional options are
|
127
|
+
`rubybreaker_opts` which is RubyBreaker's command-line options and
|
128
|
+
`break` which specifies which modules and classes to monitor. Since
|
129
|
+
`Class1` and `Class2` are not _recognized_ by this Rakefile, you must use
|
130
|
+
string literals to specify modules and classes (and with full namespace).
|
131
|
+
|
132
|
+
If this is the route you are taking, there needs no editing of the source
|
133
|
+
program whatsoever. This task will take care of instrumenting the specified
|
134
|
+
modules and classes at proper moments.
|
135
|
+
|
136
|
+
## Type Annotation
|
137
|
+
|
138
|
+
The annotation language used in RubyBreaker resembles the method
|
139
|
+
documentation used by Ruby Core Library Doc. Each type signature
|
140
|
+
defines a method type using the name, argument types, block type, and return
|
141
|
+
type. But, let us consider a simple case where there is one argument type
|
142
|
+
and a return type.
|
143
|
+
|
144
|
+
class A
|
145
|
+
...
|
146
|
+
typesig("foo(fixnum) -> string")
|
147
|
+
end
|
148
|
+
|
149
|
+
In RubyBreaker, a type signature is recognized by the meta-class level
|
150
|
+
method `typesig` which takes a string as an argument. This string is the
|
151
|
+
actual type signature written in the Ruby Type Annotation Language. This
|
152
|
+
language is designed to reflect the common documentation practice used by
|
153
|
+
Ruby Core Library Doc. It starts with the name of the method. In the
|
154
|
+
above example, `foo` is currently being given a type. The rest of the
|
155
|
+
signature takes a typical method type symbol, `(x) -> y` where `x` is the
|
156
|
+
argument type and `y` is the return type. In the example shown above, the
|
157
|
+
method takes a `Fixnum` object and returns a `String` object. Note that
|
158
|
+
these types are in lowercase, indicating they are objects and not modules or
|
159
|
+
classes themselves.
|
160
|
+
|
161
|
+
There are several types that represent an object: nominal, duck, fusion,
|
162
|
+
nil, 'any', 'or', optional, variable-length, and block. Each type signature
|
163
|
+
itself represents a method type or a method list type (explained below).
|
164
|
+
|
165
|
+
### Nominal Type
|
166
|
+
|
167
|
+
This is the simplest and most intuitive way to represent an object. For
|
168
|
+
instance, `fixnum` is an object of type `Fixnum`. Use lower-case letters and
|
169
|
+
underscores instead of _camelized_ name. `MyClass`, for example would be
|
170
|
+
`my_class` in RubyBreaker type signatures. There is no particular
|
171
|
+
reason for this convention other than it is the common practice used in
|
172
|
+
RubyDoc. Use `/` to indicate the namespace delimiter `::`. For example,
|
173
|
+
`NamspaceA::ClassB` would be represented by `namespace_a/class_b` in
|
174
|
+
a RubyBreaker type signature.
|
175
|
+
|
176
|
+
### Self Type
|
177
|
+
|
178
|
+
This type is similar to the nominal type but is referring to the current
|
179
|
+
object--that is, the receiver of the method being typed. RubyBreaker will
|
180
|
+
auto-document the return type as a self type if the return value is the same
|
181
|
+
as the receiver of that call. It is also recommended to use this type over
|
182
|
+
a nominal type (if the return value is `self`) since it depicts more
|
183
|
+
precise return type.
|
184
|
+
|
185
|
+
### Duck Type
|
186
|
+
|
187
|
+
This type is inspired by the Ruby Language's duck typing, _"if it
|
188
|
+
walks like a duck and quacks like a duck, it must be a duck."_ Using this
|
189
|
+
type, an object can be represented simply by a list of method names. For
|
190
|
+
example `[walks, quacks]` is an object that has `walks` and `quacks`
|
191
|
+
methods. Note that these method names do *not* reveal any type
|
192
|
+
information for themselves.
|
193
|
+
|
194
|
+
### Fusion Type
|
195
|
+
|
196
|
+
Duck type is very flexible but can be too lenient when trying to restrict
|
197
|
+
the type of an object. RubyBreaker provides a type called *the fusion type*
|
198
|
+
which lists method names but with respect to a nominal type. For
|
199
|
+
example, `fixnum[to_f, to_s]` represents an object that has methods `to_f`
|
200
|
+
and `to_s` whose types are same as those of `Fixnum`. This is more
|
201
|
+
restrictive (precise) than `[to_f, to_s]` because the two methods must have
|
202
|
+
the same types as `to_f` and `to_s` methods, respectively, in `Fixnum`.
|
203
|
+
|
204
|
+
### Nil Type
|
205
|
+
|
206
|
+
A nil type represents a value of nil and is denoted by `nil`.
|
207
|
+
|
208
|
+
### Any Type
|
209
|
+
|
210
|
+
RubyBreaker also provides a way to represent an object that is compatible with
|
211
|
+
any type. This type is denoted by `?`. Use caution with this type because
|
212
|
+
it should be only used for an object that requires an arbitrary yet most
|
213
|
+
specific type--that is, `?` is a subtype of any other type, but any
|
214
|
+
other type is not a subtype of `?`. This becomes a bit complicated for
|
215
|
+
method or block argument types because of their contra-variance
|
216
|
+
characteristic. Please refer to the section *Subtyping*.
|
217
|
+
|
218
|
+
### Or Type
|
219
|
+
|
220
|
+
Any above types can be "or"ed together, using `||`, to represent an object
|
221
|
+
that can be either one or the other. It _does_ not represent an object that
|
222
|
+
has to be both (which is not supported by RubyBreaker).
|
223
|
+
|
224
|
+
### Optional Argument Type and Variable-Length Argument Type
|
225
|
+
|
226
|
+
Another useful features of Ruby are the optional argument type and the
|
227
|
+
variable-length argument type. The former represents an argument that has a
|
228
|
+
default value (and therefore does not have to be provided). The latter
|
229
|
+
represents zero or more arguments of the same type. These are denoted by
|
230
|
+
suffices, `?` and `*`, respectively.
|
231
|
+
|
232
|
+
### Block Type
|
233
|
+
|
234
|
+
One of the Ruby's prominent features is the block argument. It allows
|
235
|
+
the caller to pass in a piece of code to be executed inside the callee. This
|
236
|
+
code block can be executed by the Ruby construct, `yield`, or by directly
|
237
|
+
calling the `call` method of the block object. In RubyBreaker, this type can
|
238
|
+
be respresented by curly brackets. For instance, `{|fixnum,string| ->
|
239
|
+
string}` represents a block that takes two arguments--one `Fixnum` and one
|
240
|
+
`String`--and returns a `String`.
|
241
|
+
|
242
|
+
RubyBreaker does supports nested blocks as Ruby 1.9 finally allows them.
|
243
|
+
However, *keep in mind* that RubyBreaker *cannot* automatically document the
|
244
|
+
block types due to `yield` being a language construct rather than a method,
|
245
|
+
which means it cannot be captured by meta-programming!
|
246
|
+
|
247
|
+
### Method Type and Method List Types
|
248
|
+
|
249
|
+
Method type is similar to the block type, but it represents an actual method
|
250
|
+
and not a block object. It is the "root" type that the type annotation
|
251
|
+
language supports, along with method list types. Method _list_ type is a
|
252
|
+
collection of method types to represent more than one type information for
|
253
|
+
the given method. Why would this type be needed? Consider the following Ruby
|
254
|
+
code:
|
255
|
+
|
256
|
+
def foo(x)
|
257
|
+
case x
|
258
|
+
when Fixnum
|
259
|
+
1
|
260
|
+
when String
|
261
|
+
"1"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
There is no way to document the type of `foo` without using a method list
|
266
|
+
type. Let's try to give a method type to `foo` without a method list. The
|
267
|
+
closest we can come up with would be `foo(fixnum or string) -> fixnum and
|
268
|
+
string`. But RubyBreaker does not have the "and" type in the type annotation
|
269
|
+
language because it gives me an headache! (By the way, it needs to be an
|
270
|
+
"and" type because the caller must handle both `Fixnum` and `String` return
|
271
|
+
values.)
|
272
|
+
|
273
|
+
It is a dilemma because Ruby programmers actually enjoy using this kind of
|
274
|
+
dynamic type checks in their code. To alleviate this headache, RubyBreaker
|
275
|
+
supports the method list type to represent different scenarios depending on
|
276
|
+
the argument types. Thus, the `foo` method shown above can be given the
|
277
|
+
following method list type:
|
278
|
+
|
279
|
+
typesig("foo(fixnum) -> fixnum")
|
280
|
+
typesig("foo(string) -> string")
|
281
|
+
|
282
|
+
These two type signatures simply tell RubyBreaker that `foo` has two method
|
283
|
+
types--one for a `Fixnum` argument and another for a `String` argument.
|
284
|
+
Depending on the argument type, the return type is determined. In this
|
285
|
+
example, a `Fixnum` is returned when the argument is also a `Fixnum` and a
|
286
|
+
`String` is returned when the argument is also a `String`. When
|
287
|
+
automatically documenting such a type, RubyBreaker looks for the (subtyping)
|
288
|
+
compatibility between the return types and "promote" the method type to a
|
289
|
+
method list type by spliting the type signature into two (or more in
|
290
|
+
subsequent "promotions").
|
291
|
+
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.6
|
data/bin/rubybreaker
CHANGED
@@ -23,32 +23,50 @@ module RubyBreaker
|
|
23
23
|
# Quit if there is program specified.
|
24
24
|
self.show_banner_and_exit() if ARGV.length != 1
|
25
25
|
|
26
|
+
# It is required to specify at least one library and one module/class to
|
27
|
+
# break. Otherwise, it does not make sense to run RubyBreaker. So it
|
28
|
+
# will quit.
|
29
|
+
if OPTIONS[:libs].empty?
|
30
|
+
STDERR.puts "No library is specified."
|
31
|
+
exit(1)
|
32
|
+
elsif OPTIONS[:break].empty?
|
33
|
+
STDERR.puts "No module/class is specified to break."
|
34
|
+
exit(1)
|
35
|
+
end
|
36
|
+
|
26
37
|
# Get the specified program.
|
27
|
-
|
28
|
-
|
38
|
+
prog_name = ARGV[0]
|
39
|
+
prog = File.expand_path(prog_name)
|
29
40
|
|
30
|
-
# It is ok to omit .rb extension. So try to see if
|
31
|
-
if !File.exist?(
|
32
|
-
|
41
|
+
# It is ok to omit .rb extension. So try to see if prog.rb exists
|
42
|
+
if !File.exist?(prog) && !File.extname(prog) == ".rb"
|
43
|
+
prog = "#{prog}.rb"
|
33
44
|
end
|
34
45
|
|
35
46
|
# Quit the specified program does not exist.
|
36
|
-
if !File.exist?(
|
47
|
+
if !File.exist?(prog)
|
37
48
|
fatal("#{ARGV[0]} is an invalid file.")
|
38
49
|
exit(1)
|
39
50
|
end
|
40
51
|
|
41
52
|
# Remember the program path for later use
|
42
|
-
OPTIONS[:
|
53
|
+
OPTIONS[:prog] = prog
|
43
54
|
|
44
|
-
#
|
45
|
-
|
46
|
-
|
47
|
-
|
55
|
+
# Import the libraries specified.
|
56
|
+
OPTIONS[:libs].each do |lib|
|
57
|
+
# Run the program file!
|
58
|
+
self.verbose("Importing #{lib}")
|
59
|
+
eval "require '#{lib}'", TOPLEVEL_BINDING
|
60
|
+
self.verbose("Done importing #{lib}")
|
61
|
+
end
|
48
62
|
|
49
|
-
#
|
50
|
-
|
51
|
-
|
63
|
+
# Now, RubyBreaker shell mode will run this
|
64
|
+
RubyBreaker.run()
|
65
|
+
|
66
|
+
# Run the program file!
|
67
|
+
self.verbose("Running #{prog_name}")
|
68
|
+
eval "require '#{prog}'", TOPLEVEL_BINDING
|
69
|
+
self.verbose("Done running #{prog_name}")
|
52
70
|
end
|
53
71
|
|
54
72
|
end
|
data/lib/rubybreaker/runtime.rb
CHANGED
@@ -24,37 +24,26 @@ module RubyBreaker
|
|
24
24
|
# This hash maps a (breakable) module to a type monitor
|
25
25
|
MONITOR_MAP = {} # module => monitor
|
26
26
|
|
27
|
-
|
28
|
-
# monitor.
|
29
|
-
INSTALLED = Set.new
|
27
|
+
private
|
30
28
|
|
31
|
-
#
|
32
|
-
#
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
MonitorInstaller.install_module_monitor(mod)
|
37
|
-
INSTALLED << mod
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# This method modifies specified modules/classes at the very moment
|
42
|
-
# (instead of registering them for later).
|
43
|
-
def self.breakable(*mods)
|
29
|
+
# Instruments the monitor to the specified modules/classes.
|
30
|
+
#
|
31
|
+
# TODO: monitor_type is currently not in use. Plan on using it in future
|
32
|
+
# to do type checker instrumentation
|
33
|
+
def self.install(monitor_type=:break, *mods)
|
44
34
|
mods.each do |mod|
|
45
35
|
case mod
|
46
36
|
when Array
|
47
|
-
self.
|
37
|
+
self.install(monitor_type, *mod)
|
48
38
|
when Module, Class
|
49
|
-
MonitorInstaller.
|
39
|
+
MonitorInstaller.install_monitor(mod)
|
50
40
|
eigen_class = self.eigen_class(mod)
|
51
|
-
MonitorInstaller.
|
52
|
-
INSTALLED << mod << eigen_class
|
41
|
+
MonitorInstaller.install_monitor(eigen_class)
|
53
42
|
when String, Symbol
|
54
43
|
begin
|
55
44
|
# Get the actual module and install it right now
|
56
45
|
mod = eval("#{mod}", TOPLEVEL_BINDING)
|
57
|
-
self.
|
46
|
+
self.install(monitor_type, mod) if mod
|
58
47
|
rescue NameError => e
|
59
48
|
RubyBreaker.error("#{mod} cannot be found.")
|
60
49
|
end
|
@@ -63,6 +52,31 @@ module RubyBreaker
|
|
63
52
|
end
|
64
53
|
end
|
65
54
|
end
|
55
|
+
|
56
|
+
public
|
57
|
+
|
58
|
+
# This method instruments the specified modules/classes at the time of
|
59
|
+
# the call.
|
60
|
+
def self.break(*mods)
|
61
|
+
self.install(:break, *mods)
|
62
|
+
end
|
63
|
+
|
64
|
+
# This method installs a monitor for each breakable module.
|
65
|
+
# *DEPRECATED*: Use +breakable()+ method instead.
|
66
|
+
def self.instrument()
|
67
|
+
BREAKABLES.each do |mod|
|
68
|
+
# Duplicate checks in place in these calls.
|
69
|
+
MonitorInstaller.install_monitor(mod)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# This method modifies specified modules/classes at the very moment
|
74
|
+
# (instead of registering them for later).
|
75
|
+
# *DEPRECATED*: Use +break()+ method instead
|
76
|
+
def self.breakable(*mods)
|
77
|
+
self.install(:break, *mods)
|
78
|
+
end
|
79
|
+
|
66
80
|
end
|
67
81
|
|
68
82
|
# *DEPRECATED*: Use +RubyBreaker.run()+ to indicate the point of entry.
|
@@ -70,10 +84,16 @@ module RubyBreaker
|
|
70
84
|
end
|
71
85
|
|
72
86
|
# This method just redirects to Runtime's method.
|
87
|
+
# *DEPRECATED*: Use +RubyBreaker.break()+ to indicate the point of entry.
|
73
88
|
def self.breakable(*mods)
|
74
89
|
Runtime.breakable(*mods)
|
75
90
|
end
|
76
91
|
|
92
|
+
# This method just redirects to Runtime's method.
|
93
|
+
def self.break(*mods)
|
94
|
+
Runtime.break(*mods)
|
95
|
+
end
|
96
|
+
|
77
97
|
# *DEPRECATED*: Use +Runtime.breakable()+ or +RubyBreaker.run()+ method
|
78
98
|
# instead.
|
79
99
|
module Breakable
|
data/lib/rubybreaker/task.rb
CHANGED
@@ -16,17 +16,23 @@ module Rake
|
|
16
16
|
# Rake::RubyBreakerTestTask.new(:"testtask_test") do |t|
|
17
17
|
# t.libs << "lib"
|
18
18
|
# t.test_files = ["test/testtask/tc_testtask.rb"]
|
19
|
-
# t.
|
19
|
+
# t.break = ["SampleClassA"]
|
20
20
|
# end
|
21
21
|
#
|
22
22
|
class RubyBreakerTestTask < Rake::TestTask
|
23
23
|
|
24
|
-
# List of
|
25
|
-
attr_accessor :
|
24
|
+
# List of modules/classes to break
|
25
|
+
attr_accessor :break
|
26
26
|
|
27
27
|
# RubyBreaker options
|
28
28
|
attr_accessor :rubybreaker_opts
|
29
29
|
|
30
|
+
# DEPRECATED accessor override
|
31
|
+
def breakable(); @break end
|
32
|
+
|
33
|
+
# DEPRECATED accessor override
|
34
|
+
def breakable=(*args); self.break(*args) end
|
35
|
+
|
30
36
|
# This overrides the testtask's constructor. In addition to the original
|
31
37
|
# behavior, it keeps track of RubyBreaker options and store them in a
|
32
38
|
# yaml file.
|
@@ -34,7 +40,7 @@ module Rake
|
|
34
40
|
|
35
41
|
# Initialize extra instance variables
|
36
42
|
@rubybreaker_opts = []
|
37
|
-
@
|
43
|
+
@break = nil
|
38
44
|
|
39
45
|
# Call the original constructor first
|
40
46
|
super(taskname, *args, &blk)
|
@@ -51,14 +57,14 @@ module Rake
|
|
51
57
|
|
52
58
|
# Construct the task configuration hash
|
53
59
|
config = {
|
54
|
-
name
|
55
|
-
rubybreaker_opts
|
56
|
-
|
57
|
-
test_files
|
60
|
+
:name => taskname,
|
61
|
+
:rubybreaker_opts => opts,
|
62
|
+
:break => [], # Set doesn't work well with YAML; just use an array
|
63
|
+
:test_files => @test_files,
|
58
64
|
}
|
59
65
|
|
60
66
|
# This allows a bulk declaration of Breakable modules/classes
|
61
|
-
@
|
67
|
+
@break.each { |b| config[:break] << b } if @break
|
62
68
|
|
63
69
|
# This code segment is a clever way to store yaml data in a ruby file
|
64
70
|
# that reads its own yaml data after __END__ when loaded.
|
@@ -2,13 +2,13 @@
|
|
2
2
|
# This file overrides the describe method of RSpec to call the RubyBreaker
|
3
3
|
# setup first.
|
4
4
|
|
5
|
-
RUBYBREAKER_RSPEC_PREFIX = "__rubybreaker"
|
5
|
+
RUBYBREAKER_RSPEC_PREFIX = "__rubybreaker" #:nodoc:
|
6
6
|
|
7
7
|
if defined?(RSpec)
|
8
|
-
alias :"#{RUBYBREAKER_RSPEC_PREFIX}_describe" :describe
|
8
|
+
alias :"#{RUBYBREAKER_RSPEC_PREFIX}_describe" :describe #:nodoc:
|
9
9
|
end
|
10
10
|
|
11
|
-
def describe(*args,&blk)
|
11
|
+
def describe(*args,&blk) #:nodoc:
|
12
12
|
RubyBreaker.run if defined?(RubyBreaker)
|
13
13
|
send(:"#{RUBYBREAKER_RSPEC_PREFIX}_describe", *args, &blk)
|
14
14
|
end
|
@@ -6,14 +6,14 @@
|
|
6
6
|
if defined?(Test) && defined?(Test::Unit)
|
7
7
|
|
8
8
|
# This class is patched to run RubyBreaker along with the test cases.
|
9
|
-
class Test::Unit::TestCase
|
9
|
+
class Test::Unit::TestCase #:nodoc:
|
10
10
|
|
11
11
|
# Save the original constructor method.
|
12
|
-
alias :__rubybreaker_initialize :initialize
|
12
|
+
alias :__rubybreaker_initialize :initialize #:nodoc:
|
13
13
|
|
14
14
|
# This method overrides the original constructor to run RubyBreaker before
|
15
15
|
# calling the original constructor.
|
16
|
-
def initialize(*args, &blk)
|
16
|
+
def initialize(*args, &blk) #:nodoc:
|
17
17
|
RubyBreaker.run()
|
18
18
|
return send(:__rubybreaker_initialize, *args, &blk)
|
19
19
|
end
|
data/lib/rubybreaker.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
#--
|
2
|
-
# This library dynamically
|
3
|
-
#
|
4
|
-
#
|
2
|
+
# This library dynamically instruments the source program, runs the program
|
3
|
+
# on Ruby, and produces type documentation based on the observation made
|
4
|
+
# during runtime. Although it is possible to use this as a typical Ruby
|
5
|
+
# library, our recommendation is to use it in Rakefile or use it as a
|
6
|
+
# command-line program. See TUTORIAL for more detail.
|
5
7
|
|
6
8
|
require "set"
|
7
9
|
require "optparse"
|
@@ -10,21 +12,23 @@ require_relative "rubybreaker/runtime"
|
|
10
12
|
require_relative "rubybreaker/test"
|
11
13
|
|
12
14
|
# RubyBreaker is a dynamic instrumentation and monitoring tool that
|
13
|
-
# generates type documentation for Ruby programs.
|
15
|
+
# generates type documentation automatically for Ruby programs.
|
14
16
|
module RubyBreaker
|
15
17
|
include TypeDefs
|
16
18
|
include Runtime
|
17
19
|
|
18
20
|
# Options for RubyBreaker
|
19
21
|
OPTIONS = {
|
20
|
-
:debug
|
21
|
-
:style
|
22
|
-
:io_file
|
23
|
-
:append
|
24
|
-
:stdout
|
25
|
-
:verbose
|
22
|
+
:debug => false, # in debug mode?
|
23
|
+
:style => :underscore, # type signature style-underscore or camelize
|
24
|
+
:io_file => nil, # generate input/output other than default?
|
25
|
+
:append => false, # append to the input file (if there is)?
|
26
|
+
:stdout => false, # also display on the screen?
|
27
|
+
:verbose => false, # in RubyBreaker.verbose mode?
|
26
28
|
:save_output => true, # save output to a file?
|
27
|
-
:
|
29
|
+
:break => [], # modules to break
|
30
|
+
:libs => [], # list of library files to import
|
31
|
+
:prog => nil, # program or test file
|
28
32
|
}
|
29
33
|
|
30
34
|
# This option parser may be used for the command-line mode or for the
|
@@ -34,6 +38,16 @@ module RubyBreaker
|
|
34
38
|
|
35
39
|
opts.banner = "Usage: #{File.basename(__FILE__)} [options] prog[.rb]"
|
36
40
|
|
41
|
+
opts.on("-b MODULES", "--break MODULES", "Specify modules/classes to 'break'") do |s|
|
42
|
+
tokens = s.split(/[;,]/)
|
43
|
+
tokens.each {|t| OPTIONS[:break] << t}
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on("-l LIBRARIES", "--libs LIBRARIES", "Specify libraries to load") do |s|
|
47
|
+
tokens = s.split(":")
|
48
|
+
tokens.each {|t| OPTIONS[:libs] << t}
|
49
|
+
end
|
50
|
+
|
37
51
|
opts.on("--debug", "Run in debug mode") do
|
38
52
|
OPTIONS[:debug] = true
|
39
53
|
end
|
@@ -104,7 +118,7 @@ module RubyBreaker
|
|
104
118
|
code = ""
|
105
119
|
|
106
120
|
# Document each module that was monitored.
|
107
|
-
|
121
|
+
MONITOR_MAP.each_key { |mod|
|
108
122
|
str = Runtime::TypeSigUnparser.unparse(mod)
|
109
123
|
code << str
|
110
124
|
print str if OPTIONS[:stdout] # display on the screen if requested
|
@@ -157,12 +171,13 @@ module RubyBreaker
|
|
157
171
|
RubyBreaker.verbose("Running RubyBreaker within a testcase")
|
158
172
|
task = self.task
|
159
173
|
OPTION_PARSER.parse(*task[:rubybreaker_opts])
|
160
|
-
Runtime.
|
174
|
+
Runtime.break(*task[:break])
|
161
175
|
task_name = task[:name]
|
162
176
|
RubyBreaker.verbose("Done reading task information")
|
163
177
|
io_file = self.io_file(task_name)
|
164
|
-
elsif OPTIONS[:
|
165
|
-
Runtime.
|
178
|
+
elsif OPTIONS[:prog] # running in shell mode
|
179
|
+
Runtime.break(*mods) # should not happen but for backward-compatibility
|
180
|
+
Runtime.break(*OPTIONS[:break])
|
166
181
|
io_file = self.io_file(OPTIONS[:prog_file])
|
167
182
|
else
|
168
183
|
# Otherwise, assume there are no explicit IO files.
|
@@ -179,7 +194,7 @@ module RubyBreaker
|
|
179
194
|
end
|
180
195
|
|
181
196
|
# This method is available by default.
|
182
|
-
module Kernel
|
197
|
+
module Kernel #:nodoc:
|
183
198
|
|
184
199
|
def typesig(str)
|
185
200
|
_TypeDefs = RubyBreaker::TypeDefs
|
@@ -1,21 +1,20 @@
|
|
1
1
|
require "test/unit"
|
2
2
|
require_relative "../../lib/rubybreaker"
|
3
3
|
|
4
|
-
class
|
4
|
+
class IntegratedBothDocumentedAndUndocumented < Test::Unit::TestCase
|
5
5
|
include RubyBreaker
|
6
6
|
|
7
7
|
class A
|
8
|
-
# include RubyBreaker::Breakable
|
9
8
|
typesig("foo(fixnum[to_s]) -> string")
|
10
9
|
def foo(x); x.to_s end
|
11
10
|
def bar(x); x.to_sym end
|
12
11
|
end
|
13
12
|
|
14
13
|
def setup
|
15
|
-
RubyBreaker.
|
14
|
+
RubyBreaker.break(A)
|
16
15
|
end
|
17
16
|
|
18
|
-
def
|
17
|
+
def test_both_documented_and_undocumented
|
19
18
|
A.new.bar("abc")
|
20
19
|
a_foo_meth_type = Runtime::Inspector.inspect_meth(A, :foo)
|
21
20
|
a_bar_meth_type = Runtime::Inspector.inspect_meth(A, :bar)
|