rubybreaker 0.0.5 → 0.0.6
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/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)
|