rushcheck 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +515 -0
- data/INSTALL +14 -0
- data/LICENSE +5 -0
- data/README +49 -0
- data/Rakefile +39 -0
- data/copying.txt +23 -0
- data/data/rushcheck/doc/policy.txt +520 -0
- data/data/rushcheck/doc/rushcheck.txt +612 -0
- data/data/rushcheck/examples/printf.rb +22 -0
- data/data/rushcheck/examples/proc.rb +33 -0
- data/data/rushcheck/examples/roguetile.rb +153 -0
- data/data/rushcheck/examples/sample.rb +52 -0
- data/data/rushcheck/rdoc/classes/Arbitrary.html +148 -0
- data/data/rushcheck/rdoc/classes/Arbitrary.src/M000075.html +18 -0
- data/data/rushcheck/rdoc/classes/Assertion.html +183 -0
- data/data/rushcheck/rdoc/classes/Assertion.src/M000015.html +20 -0
- data/data/rushcheck/rdoc/classes/Assertion.src/M000016.html +45 -0
- data/data/rushcheck/rdoc/classes/Coarbitrary.html +135 -0
- data/data/rushcheck/rdoc/classes/Coarbitrary.src/M000095.html +18 -0
- data/data/rushcheck/rdoc/classes/FalseClass.html +177 -0
- data/data/rushcheck/rdoc/classes/FalseClass.src/M000018.html +18 -0
- data/data/rushcheck/rdoc/classes/FalseClass.src/M000019.html +18 -0
- data/data/rushcheck/rdoc/classes/FalseClass.src/M000020.html +18 -0
- data/data/rushcheck/rdoc/classes/Float.html +191 -0
- data/data/rushcheck/rdoc/classes/Float.src/M000047.html +21 -0
- data/data/rushcheck/rdoc/classes/Float.src/M000048.html +18 -0
- data/data/rushcheck/rdoc/classes/Float.src/M000049.html +22 -0
- data/data/rushcheck/rdoc/classes/Float.src/M000050.html +20 -0
- data/data/rushcheck/rdoc/classes/Gen.html +515 -0
- data/data/rushcheck/rdoc/classes/Gen.src/M000026.html +18 -0
- data/data/rushcheck/rdoc/classes/Gen.src/M000027.html +20 -0
- data/data/rushcheck/rdoc/classes/Gen.src/M000028.html +28 -0
- data/data/rushcheck/rdoc/classes/Gen.src/M000029.html +24 -0
- data/data/rushcheck/rdoc/classes/Gen.src/M000030.html +18 -0
- data/data/rushcheck/rdoc/classes/Gen.src/M000031.html +18 -0
- data/data/rushcheck/rdoc/classes/Gen.src/M000032.html +18 -0
- data/data/rushcheck/rdoc/classes/Gen.src/M000033.html +18 -0
- data/data/rushcheck/rdoc/classes/Gen.src/M000034.html +18 -0
- data/data/rushcheck/rdoc/classes/Gen.src/M000035.html +24 -0
- data/data/rushcheck/rdoc/classes/Gen.src/M000036.html +18 -0
- data/data/rushcheck/rdoc/classes/Gen.src/M000037.html +21 -0
- data/data/rushcheck/rdoc/classes/Gen.src/M000038.html +18 -0
- data/data/rushcheck/rdoc/classes/Gen.src/M000039.html +18 -0
- data/data/rushcheck/rdoc/classes/Gen.src/M000040.html +23 -0
- data/data/rushcheck/rdoc/classes/Gen.src/M000041.html +19 -0
- data/data/rushcheck/rdoc/classes/Gen.src/M000042.html +18 -0
- data/data/rushcheck/rdoc/classes/Gen.src/M000043.html +24 -0
- data/data/rushcheck/rdoc/classes/Guard.html +159 -0
- data/data/rushcheck/rdoc/classes/Guard.src/M000024.html +27 -0
- data/data/rushcheck/rdoc/classes/Guard.src/M000025.html +18 -0
- data/data/rushcheck/rdoc/classes/HsRandom.html +201 -0
- data/data/rushcheck/rdoc/classes/HsRandom.src/M000081.html +22 -0
- data/data/rushcheck/rdoc/classes/HsRandom.src/M000082.html +32 -0
- data/data/rushcheck/rdoc/classes/HsRandom.src/M000083.html +18 -0
- data/data/rushcheck/rdoc/classes/Integer.html +212 -0
- data/data/rushcheck/rdoc/classes/Integer.src/M000006.html +18 -0
- data/data/rushcheck/rdoc/classes/Integer.src/M000007.html +18 -0
- data/data/rushcheck/rdoc/classes/Integer.src/M000008.html +21 -0
- data/data/rushcheck/rdoc/classes/Integer.src/M000009.html +19 -0
- data/data/rushcheck/rdoc/classes/NilClass.html +177 -0
- data/data/rushcheck/rdoc/classes/NilClass.src/M000059.html +18 -0
- data/data/rushcheck/rdoc/classes/NilClass.src/M000060.html +18 -0
- data/data/rushcheck/rdoc/classes/NilClass.src/M000061.html +18 -0
- data/data/rushcheck/rdoc/classes/Property.html +174 -0
- data/data/rushcheck/rdoc/classes/Property.src/M000070.html +26 -0
- data/data/rushcheck/rdoc/classes/Property.src/M000071.html +18 -0
- data/data/rushcheck/rdoc/classes/RandomArray.html +184 -0
- data/data/rushcheck/rdoc/classes/RandomArray.src/M000021.html +18 -0
- data/data/rushcheck/rdoc/classes/RandomArray.src/M000022.html +35 -0
- data/data/rushcheck/rdoc/classes/RandomArray.src/M000023.html +22 -0
- data/data/rushcheck/rdoc/classes/RandomBool.html +146 -0
- data/data/rushcheck/rdoc/classes/RandomBool.src/M000079.html +18 -0
- data/data/rushcheck/rdoc/classes/RandomBool.src/M000080.html +19 -0
- data/data/rushcheck/rdoc/classes/RandomGen.html +196 -0
- data/data/rushcheck/rdoc/classes/RandomGen.src/M000076.html +18 -0
- data/data/rushcheck/rdoc/classes/RandomGen.src/M000077.html +18 -0
- data/data/rushcheck/rdoc/classes/RandomGen.src/M000078.html +18 -0
- data/data/rushcheck/rdoc/classes/RandomHash.html +197 -0
- data/data/rushcheck/rdoc/classes/RandomHash.src/M000044.html +18 -0
- data/data/rushcheck/rdoc/classes/RandomHash.src/M000045.html +26 -0
- data/data/rushcheck/rdoc/classes/RandomHash.src/M000046.html +22 -0
- data/data/rushcheck/rdoc/classes/RandomProc.html +192 -0
- data/data/rushcheck/rdoc/classes/RandomProc.src/M000055.html +18 -0
- data/data/rushcheck/rdoc/classes/RandomProc.src/M000056.html +30 -0
- data/data/rushcheck/rdoc/classes/RandomProc.src/M000057.html +26 -0
- data/data/rushcheck/rdoc/classes/RandomProc.src/M000058.html +20 -0
- data/data/rushcheck/rdoc/classes/Result.html +214 -0
- data/data/rushcheck/rdoc/classes/Result.src/M000051.html +18 -0
- data/data/rushcheck/rdoc/classes/Result.src/M000052.html +18 -0
- data/data/rushcheck/rdoc/classes/Result.src/M000053.html +18 -0
- data/data/rushcheck/rdoc/classes/RushCheckConfig.html +240 -0
- data/data/rushcheck/rdoc/classes/RushCheckConfig.src/M000001.html +23 -0
- data/data/rushcheck/rdoc/classes/RushCheckConfig.src/M000002.html +22 -0
- data/data/rushcheck/rdoc/classes/RushCheckConfig.src/M000003.html +18 -0
- data/data/rushcheck/rdoc/classes/RushCheckConfig.src/M000004.html +38 -0
- data/data/rushcheck/rdoc/classes/RushCheckConfig.src/M000005.html +55 -0
- data/data/rushcheck/rdoc/classes/RushCheckGuard.html +118 -0
- data/data/rushcheck/rdoc/classes/SpecialString.html +151 -0
- data/data/rushcheck/rdoc/classes/SpecialString.src/M000017.html +37 -0
- data/data/rushcheck/rdoc/classes/StdGen.html +252 -0
- data/data/rushcheck/rdoc/classes/StdGen.src/M000010.html +23 -0
- data/data/rushcheck/rdoc/classes/StdGen.src/M000011.html +21 -0
- data/data/rushcheck/rdoc/classes/StdGen.src/M000012.html +21 -0
- data/data/rushcheck/rdoc/classes/StdGen.src/M000013.html +18 -0
- data/data/rushcheck/rdoc/classes/StdGen.src/M000014.html +18 -0
- data/data/rushcheck/rdoc/classes/String.html +191 -0
- data/data/rushcheck/rdoc/classes/String.src/M000066.html +24 -0
- data/data/rushcheck/rdoc/classes/String.src/M000067.html +18 -0
- data/data/rushcheck/rdoc/classes/String.src/M000068.html +25 -0
- data/data/rushcheck/rdoc/classes/String.src/M000069.html +22 -0
- data/data/rushcheck/rdoc/classes/Testable.html +281 -0
- data/data/rushcheck/rdoc/classes/Testable.src/M000084.html +18 -0
- data/data/rushcheck/rdoc/classes/Testable.src/M000085.html +18 -0
- data/data/rushcheck/rdoc/classes/Testable.src/M000088.html +18 -0
- data/data/rushcheck/rdoc/classes/Testable.src/M000089.html +18 -0
- data/data/rushcheck/rdoc/classes/Testable.src/M000090.html +18 -0
- data/data/rushcheck/rdoc/classes/Testable.src/M000092.html +18 -0
- data/data/rushcheck/rdoc/classes/Testable.src/M000094.html +18 -0
- data/data/rushcheck/rdoc/classes/TheStdGen.html +200 -0
- data/data/rushcheck/rdoc/classes/TheStdGen.src/M000062.html +18 -0
- data/data/rushcheck/rdoc/classes/TheStdGen.src/M000063.html +20 -0
- data/data/rushcheck/rdoc/classes/TheStdGen.src/M000064.html +21 -0
- data/data/rushcheck/rdoc/classes/TheStdGen.src/M000065.html +18 -0
- data/data/rushcheck/rdoc/classes/TrueClass.html +177 -0
- data/data/rushcheck/rdoc/classes/TrueClass.src/M000072.html +18 -0
- data/data/rushcheck/rdoc/classes/TrueClass.src/M000073.html +18 -0
- data/data/rushcheck/rdoc/classes/TrueClass.src/M000074.html +18 -0
- data/data/rushcheck/rdoc/created.rid +1 -0
- data/data/rushcheck/rdoc/files/rushcheck/arbitrary_rb.html +114 -0
- data/data/rushcheck/rdoc/files/rushcheck/array_rb.html +117 -0
- data/data/rushcheck/rdoc/files/rushcheck/assertion_rb.html +120 -0
- data/data/rushcheck/rdoc/files/rushcheck/bool_rb.html +120 -0
- data/data/rushcheck/rdoc/files/rushcheck/config_rb.html +109 -0
- data/data/rushcheck/rdoc/files/rushcheck/float_rb.html +118 -0
- data/data/rushcheck/rdoc/files/rushcheck/gen_rb.html +111 -0
- data/data/rushcheck/rdoc/files/rushcheck/guard_rb.html +108 -0
- data/data/rushcheck/rdoc/files/rushcheck/hash_rb.html +117 -0
- data/data/rushcheck/rdoc/files/rushcheck/integer_rb.html +118 -0
- data/data/rushcheck/rdoc/files/rushcheck/proc_rb.html +118 -0
- data/data/rushcheck/rdoc/files/rushcheck/property_rb.html +119 -0
- data/data/rushcheck/rdoc/files/rushcheck/random_rb.html +126 -0
- data/data/rushcheck/rdoc/files/rushcheck/result_rb.html +118 -0
- data/data/rushcheck/rdoc/files/rushcheck/rushcheck_rb.html +123 -0
- data/data/rushcheck/rdoc/files/rushcheck/string_rb.html +121 -0
- data/data/rushcheck/rdoc/files/rushcheck/testable_rb.html +117 -0
- data/data/rushcheck/rdoc/fr_class_index.html +51 -0
- data/data/rushcheck/rdoc/fr_file_index.html +43 -0
- data/data/rushcheck/rdoc/fr_method_index.html +121 -0
- data/data/rushcheck/rdoc/index.html +24 -0
- data/data/rushcheck/rdoc/rdoc-style.css +208 -0
- data/lib/rushcheck/arbitrary.rb +28 -0
- data/lib/rushcheck/array.rb +49 -0
- data/lib/rushcheck/assertion.rb +67 -0
- data/lib/rushcheck/bool.rb +90 -0
- data/lib/rushcheck/config.rb +98 -0
- data/lib/rushcheck/float.rb +43 -0
- data/lib/rushcheck/gen.rb +189 -0
- data/lib/rushcheck/guard.rb +27 -0
- data/lib/rushcheck/hash.rb +45 -0
- data/lib/rushcheck/integer.rb +43 -0
- data/lib/rushcheck/proc.rb +53 -0
- data/lib/rushcheck/property.rb +29 -0
- data/lib/rushcheck/random.rb +215 -0
- data/lib/rushcheck/result.rb +26 -0
- data/lib/rushcheck/rushcheck.rb +17 -0
- data/lib/rushcheck/string.rb +100 -0
- data/lib/rushcheck/testable.rb +41 -0
- metadata +238 -0
@@ -0,0 +1,612 @@
|
|
1
|
+
RushCheck - a random testing tool for Ruby
|
2
|
+
Tutorial for version 0.2
|
3
|
+
2006-08-08 Daisuke IKEGAMI <ikegami@madscientist.jp>
|
4
|
+
|
5
|
+
This document is a draft and may be obsoleted in future.
|
6
|
+
Check also the web page
|
7
|
+
http://rushcheck.rubyforge.org
|
8
|
+
|
9
|
+
= Getting started
|
10
|
+
|
11
|
+
== Install
|
12
|
+
|
13
|
+
% ruby setup.rb
|
14
|
+
|
15
|
+
at the root directory of the source code. The installation should be
|
16
|
+
easy. If not, unfortunately, please let me know. The author want to
|
17
|
+
distribute the library as a rubygem in future.
|
18
|
+
|
19
|
+
== Tutorial for writing testcase
|
20
|
+
|
21
|
+
1. add the following declaration to load RushCheck library
|
22
|
+
|
23
|
+
require 'rushcheck/rushcheck'
|
24
|
+
|
25
|
+
2. and require also your library file if the class you want to test is
|
26
|
+
included in it.
|
27
|
+
|
28
|
+
require 'your_library'
|
29
|
+
|
30
|
+
3. How to write assertions:
|
31
|
+
|
32
|
+
OK, then we can start to write test codes. RushCheck requires to
|
33
|
+
write /assertion based/ testcases. An assertion of function (or
|
34
|
+
method) consists of triple where inputs, guard conditions and a
|
35
|
+
testing property block. Here is a templete of assertion:
|
36
|
+
|
37
|
+
Assertion.new(Class1, Class2, ...) do |x, y, z, ..., g1, g2, ...|
|
38
|
+
# testing property block
|
39
|
+
# this block should return boolean value (true, false or nil)
|
40
|
+
# in Ruby
|
41
|
+
end
|
42
|
+
|
43
|
+
For example, assume that we want to test the method 'sort' in
|
44
|
+
Array. The following assertion is a simple testcase:
|
45
|
+
|
46
|
+
ast_zero =
|
47
|
+
Assertion.new() do
|
48
|
+
[].sort == []
|
49
|
+
end
|
50
|
+
|
51
|
+
whether if we sort empty array the result is also empty.
|
52
|
+
This assertion does not have any inputs and guards but only have
|
53
|
+
the property block.
|
54
|
+
|
55
|
+
Let's see another example:
|
56
|
+
|
57
|
+
ast_one =
|
58
|
+
Assertion.new(Integer) do |x|
|
59
|
+
[x].sort == [x]
|
60
|
+
end
|
61
|
+
|
62
|
+
This assertion defines that we claim for any integer 'x', an array
|
63
|
+
[x] is not changed after sorting. We can test the property 100 times:
|
64
|
+
|
65
|
+
irb> ast_one.verbose_check
|
66
|
+
1:
|
67
|
+
[-1]
|
68
|
+
2:
|
69
|
+
[-2]
|
70
|
+
... (snip) ...
|
71
|
+
99:
|
72
|
+
[6]
|
73
|
+
100:
|
74
|
+
[1]
|
75
|
+
OK, passed 100 tests.
|
76
|
+
true
|
77
|
+
irb> ast_one.check
|
78
|
+
OK, passed 100 tests.
|
79
|
+
true
|
80
|
+
irb>
|
81
|
+
|
82
|
+
RushCheck supports random testing such as above. We will see later
|
83
|
+
how to change the number of test, how to change the distribution,
|
84
|
+
etc. Here we learned two testing methods, verbose_check and check.
|
85
|
+
'verbose_check' displays every instances of test, on the other hand
|
86
|
+
'check' does not display inputs but only the result.
|
87
|
+
|
88
|
+
Next example shows how RushCheck displays the counter example. If
|
89
|
+
an assertion is failed, then there is a counter example of the
|
90
|
+
assertion.
|
91
|
+
|
92
|
+
ast_two =
|
93
|
+
Assertion.new(Integer, Integer) do |x, y|
|
94
|
+
[x, y].sort == [x, y]
|
95
|
+
end
|
96
|
+
|
97
|
+
The above test is failed sometimes and we can find the result after
|
98
|
+
checking.
|
99
|
+
|
100
|
+
irb> ast_two.check
|
101
|
+
Falsifiable, after 3 tests:
|
102
|
+
[2, -1]
|
103
|
+
false
|
104
|
+
irb>
|
105
|
+
|
106
|
+
Here, the counter example [2, -1] of the assertion is appeared. In
|
107
|
+
fact, [2, -1].sort equals [-1, 2] and does not equal [2, -1].
|
108
|
+
|
109
|
+
Sometimes, we need several pre-conditions for tests. For example,
|
110
|
+
if we have a pre-condition 'x <= y' in the previous assertion, then
|
111
|
+
the assertion should be succeeded. We can write pre-conditions as
|
112
|
+
guards in the property block:
|
113
|
+
|
114
|
+
ast_two_sorted =
|
115
|
+
Assertion.new(Integer, Integer) do |x, y, g|
|
116
|
+
g.guard { x <= y }
|
117
|
+
[x, y].sort == [x, y]
|
118
|
+
end
|
119
|
+
|
120
|
+
irb> ast_two_sorted.check
|
121
|
+
OK, passed 100 tests.
|
122
|
+
true
|
123
|
+
irb>
|
124
|
+
|
125
|
+
In this example, the number of arguments of Assertion.new (equals
|
126
|
+
2) is less than the number of variables of the property block
|
127
|
+
(equals 3). It is always assumed that the number of arguments of
|
128
|
+
Assertion.new is equal or less than the number of variables of the
|
129
|
+
block. The arguments of Assertion.new corresponds to the variables
|
130
|
+
of block one to one. Then the rest of variables is assumed as guard
|
131
|
+
conditions (in this example, the variable g is not corresponded and
|
132
|
+
assumed as a guard). We can have any number of guards in the
|
133
|
+
property block. If the guard property does not hold in random
|
134
|
+
testing, then the test is abort and try to take another random
|
135
|
+
instances. We can imagine the following test sequences in
|
136
|
+
ast_two_sorted.
|
137
|
+
|
138
|
+
1: x, y = 1, 2
|
139
|
+
--> guard g is passed
|
140
|
+
--> check the test block and succeed
|
141
|
+
2: x, y = 3, 1
|
142
|
+
--> guard g is failed and abort
|
143
|
+
(not counted)
|
144
|
+
3: x, y = 2, 3
|
145
|
+
--> ...
|
146
|
+
... (whether passed 100 tests or not)
|
147
|
+
|
148
|
+
4. Watching the statistics:
|
149
|
+
|
150
|
+
In the previous section, we saw two methods 'check' and
|
151
|
+
'verbose_check'. Sometimes, we want to watch the statistics of
|
152
|
+
random testing. However 'check' shows less information, and
|
153
|
+
'verbose_check' is too noisy. RushCheck has several options to
|
154
|
+
watch the statistics.
|
155
|
+
|
156
|
+
4.1 'trivial'
|
157
|
+
|
158
|
+
You may not want to check the trivial case in random test. As we
|
159
|
+
have seen, we can ignore the trivial case using the guard
|
160
|
+
condition. However, on the other hand, we can watch how many
|
161
|
+
trivial cases are appeared in random test.
|
162
|
+
|
163
|
+
ast_sort_triv =
|
164
|
+
Assertion.new(Integer, Integer) do |x, y, g|
|
165
|
+
g.guard {x <= y}
|
166
|
+
([x, y].sort == [x, y]).trivial{ x == y }
|
167
|
+
end
|
168
|
+
|
169
|
+
irb> ast_sort_triv.check
|
170
|
+
OK, passed 100 tests(14%, trivial).
|
171
|
+
true
|
172
|
+
|
173
|
+
Here, we have 14% (i.e. 14 times) trivial (x == y) cases in the
|
174
|
+
test.
|
175
|
+
|
176
|
+
4.2 'classify'
|
177
|
+
|
178
|
+
In addition, we can give another names to watching statistics.
|
179
|
+
ast_sort_classify =
|
180
|
+
Assertion.new(Integer, Integer) do |x, y, g|
|
181
|
+
g.guard {x <= y}
|
182
|
+
test = ([x, y].sort == [x, y])
|
183
|
+
test.classify('same'){ x == y }.
|
184
|
+
classify('bit diff'){ (x - y).abs == 1 }
|
185
|
+
end
|
186
|
+
|
187
|
+
irb> ast_sort_classify.check
|
188
|
+
OK, passed 100 tests.
|
189
|
+
18%, same.
|
190
|
+
11%, bit diff.
|
191
|
+
true
|
192
|
+
irb>
|
193
|
+
|
194
|
+
5. With another basic classes for assertions
|
195
|
+
|
196
|
+
In previous sections, we have seen how to check assertions for any
|
197
|
+
integers. In similar way, we can define the assertions for any float
|
198
|
+
numbers or any string.
|
199
|
+
|
200
|
+
Assertion.new(Float, String, ...) do |x, y,..., g, ...|
|
201
|
+
# ...
|
202
|
+
end
|
203
|
+
|
204
|
+
RushCheck has default random generators for the following basic classes:
|
205
|
+
- Integer
|
206
|
+
- Float
|
207
|
+
- String
|
208
|
+
If you want to change the distribution of randomness, then you have
|
209
|
+
to write a code for generator. There are some examples for writing
|
210
|
+
generators. In the next section, I will introduce SpecialString
|
211
|
+
whose distribution differs to the default implementation of String.
|
212
|
+
|
213
|
+
Even Array, Hash and Proc are also primitive classes, however the
|
214
|
+
randomness of them should be changed in testing codes. Therefore,
|
215
|
+
RushCheck provides an abstract generators for them. Programmer need
|
216
|
+
to write a subclass of the abstract generators. In section 5.2 and
|
217
|
+
later I will show you how to write a subclass of RandomArray, etc.
|
218
|
+
|
219
|
+
5.1 SpecialString
|
220
|
+
|
221
|
+
Sometimes, we want to check special characters, for example the
|
222
|
+
backslash '\' or unprinted characters 'ESC', 'NUL' and so on.
|
223
|
+
SpecialString is a subclass of String which is defined in
|
224
|
+
'rushcheck/string'. This library is already required in
|
225
|
+
'rushcheck/rushcheck' and don't need to require again.
|
226
|
+
|
227
|
+
The output of random generator of SpecialString has different
|
228
|
+
distribution of its of String. At String, the distribution of
|
229
|
+
characters are normalized. On the other hand, the distribution in
|
230
|
+
SpecialString is weighted and the generator provides special
|
231
|
+
characters more often than alphabets. In detail, the distribution is
|
232
|
+
as follows:
|
233
|
+
|
234
|
+
the distribution of SpecialString
|
235
|
+
---------------------------------------
|
236
|
+
Alphabet ... 15 %
|
237
|
+
Control characters ... 50 % (such as NUL)
|
238
|
+
Number ... 10 %
|
239
|
+
Special characters ... 25 % (such as '\')
|
240
|
+
|
241
|
+
Using SpecialString in assertions, it is more likely to find the
|
242
|
+
counter example. The following example is the famous bug which is
|
243
|
+
called 'malformed format bug'.
|
244
|
+
|
245
|
+
malformed_format_string =
|
246
|
+
Assertion.new(String) { |s| sprintf(s); true}
|
247
|
+
|
248
|
+
malformed_format_string2 =
|
249
|
+
Assertion.new(SpecialString) { |s| sprintf(s); true}
|
250
|
+
|
251
|
+
irb> malformed_format_string.check
|
252
|
+
Falsifiable, after 86 tests:
|
253
|
+
Unexpected exception: #<ArgumentError: malformed format string - %&>
|
254
|
+
... snip error traces ...
|
255
|
+
["\n&'e!]hr(%&\031Vi\003 }ss"]
|
256
|
+
false
|
257
|
+
irb> malformed_format_string2.check
|
258
|
+
Falsifiable, after 15 tests:
|
259
|
+
Unexpected exception: #<ArgumentError: malformed format string>
|
260
|
+
["%\037-R"]
|
261
|
+
false
|
262
|
+
|
263
|
+
In these results, we can see RushCheck find the counter example
|
264
|
+
after 86 tests with String, on the other hand, find it quickly after 15
|
265
|
+
tests with SpecialString.
|
266
|
+
|
267
|
+
It is easy to change the distribution in SpecialString. You can
|
268
|
+
define your subclass of SpecialString as follows:
|
269
|
+
|
270
|
+
class YourSpecialString < SpecialString
|
271
|
+
@@frequency = { 'alphabet' => 1,
|
272
|
+
'control' => 0,
|
273
|
+
'number' => 0,
|
274
|
+
'special' => 9 }
|
275
|
+
end
|
276
|
+
|
277
|
+
5.2 Array and RandomArray
|
278
|
+
|
279
|
+
The meaning of randomness for Array must be changed in testing
|
280
|
+
codes. Sometimes you needs a random array of Integer, and another a
|
281
|
+
random array of String. So what is random array?
|
282
|
+
|
283
|
+
RushCheck provides an abstract class RandomArray for abstract random
|
284
|
+
generator. Programmer have to write a subclass of RandomArray as
|
285
|
+
follows:
|
286
|
+
|
287
|
+
# for random array of integers
|
288
|
+
class MyRandomArray < RandomArray; end
|
289
|
+
MyRandomArray.set_pattern(Integer) {|ary, i| Integer}
|
290
|
+
|
291
|
+
(I don't like the name set_pattern and may change it in future)
|
292
|
+
|
293
|
+
The class method set_pattern takes a variable and a block.
|
294
|
+
Because array is /inductive/ structure, it can be defined by the
|
295
|
+
basecase and the inductive step.
|
296
|
+
|
297
|
+
For example, let's consider a random array
|
298
|
+
[Integer, String, Integer, String, ...]
|
299
|
+
where it has Integer at the odd index and String at the even index.
|
300
|
+
Then we can write a random array with this pattern:
|
301
|
+
|
302
|
+
MyRandomArray.set_pattern(Integer) do |ary, i|
|
303
|
+
if i % 2 == 0
|
304
|
+
then String
|
305
|
+
else Integer
|
306
|
+
end
|
307
|
+
|
308
|
+
More complecated example? OK, let's consider a stream:
|
309
|
+
the first component is Integer
|
310
|
+
if the i-th component is positive integer
|
311
|
+
then (i+1)-th component is String
|
312
|
+
otherwise the (i+1)-th component is Integer
|
313
|
+
|
314
|
+
MyRandomArray.set_pattern(Integer) do |ary, i|
|
315
|
+
if ary[i].kind_of?(Integer) && ary[i] >= 0
|
316
|
+
then String
|
317
|
+
else Integer
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
In this way, we can define any random array with any pattern.
|
322
|
+
|
323
|
+
5.3 Hash and RandomHash
|
324
|
+
|
325
|
+
Hash is also primitive and not so clear what is a random hash, like
|
326
|
+
array. RushCheck provides an abstract random generator RandomHash,
|
327
|
+
and programmer can write a subclass of RandomHash.
|
328
|
+
|
329
|
+
class MyRandomHash < RandomHash; end
|
330
|
+
pat = { 'key1' => Integer, 'key2' => String }
|
331
|
+
MyRandomHash.set_pattern(pat)
|
332
|
+
|
333
|
+
In this example, we can get a random Hash with two keys ('key1' and
|
334
|
+
'key2'). Here the keys are String, but we can give any object as
|
335
|
+
usual in Hash. Is it clear to define random hash? I think so.
|
336
|
+
(If not it is clear, then the interface may be changed in future)
|
337
|
+
|
338
|
+
5.4 Proc and RandomProc
|
339
|
+
|
340
|
+
It is not difficult to create a random Proc object.
|
341
|
+
As we saw in the previous sections, we have to write a subclass of
|
342
|
+
RandomProc.
|
343
|
+
|
344
|
+
class MyRandomProc < RandomProc; end
|
345
|
+
MyRandomProc.set_pattern([Integer], [Integer])
|
346
|
+
|
347
|
+
Here, we define a random procedure which takes an integer and
|
348
|
+
returns an integer also. In general, Ruby's function and method can
|
349
|
+
be regarded as a relation. (not a function in mathematics!)
|
350
|
+
Therefore I think random procedures can be generated by a pair of
|
351
|
+
inputs and outputs.
|
352
|
+
|
353
|
+
Let's consider a simple example. In general, any functions f, g, and
|
354
|
+
h should satisfy the associativity property:
|
355
|
+
for all x, f(g(h(x)) === (f . g)(h (x))
|
356
|
+
where f . g is a composition of functions in mathematical way
|
357
|
+
|
358
|
+
class Proc
|
359
|
+
# application
|
360
|
+
def **(other)
|
361
|
+
Proc.new do |*args|
|
362
|
+
res = other.call(*args)
|
363
|
+
call(*res)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
class MyRandomProc < RandomProc; end
|
369
|
+
|
370
|
+
def associativity_integer
|
371
|
+
MyRandomProc.set_pattern([Integer], [Integer])
|
372
|
+
Assertion.new(MyRandomProc, MyRandomProc, MyRandomProc, Integer) do
|
373
|
+
|a, b, c, x|
|
374
|
+
(a ** (b ** c)).call(x) == ((a ** b) ** c).call(x)
|
375
|
+
end.check
|
376
|
+
end
|
377
|
+
|
378
|
+
P.S.
|
379
|
+
The arbitrary method is used to create a random object for test
|
380
|
+
instance. Then you may wonder what is the coarbitrary method?
|
381
|
+
The coarbitrary method is used to generate a random procedure
|
382
|
+
(lambda), which is one of central idea in QuickCheck.
|
383
|
+
|
384
|
+
6. How to define random generators for user defined class
|
385
|
+
|
386
|
+
6.1 Understand your class. What is a random object?
|
387
|
+
|
388
|
+
To use your class in the assertion, like follows
|
389
|
+
Assertion.new(YourClass) {...}
|
390
|
+
you have to write a code which generates a random object in your
|
391
|
+
class. Therefore, at first we have to consider
|
392
|
+
what is a random object in YourClass?
|
393
|
+
|
394
|
+
Sometimes the answer is not unique; there may be several ways for
|
395
|
+
generating random objects. Like SpecialString in RushCheck, you can
|
396
|
+
also write an abstract random generator.
|
397
|
+
|
398
|
+
OK, after thinking about the question, we have to write a code.
|
399
|
+
To define random generators, we have to add a class method arbitrary
|
400
|
+
in YourClass.
|
401
|
+
|
402
|
+
require 'rushcheck/rushcheck'
|
403
|
+
class YourClass
|
404
|
+
extend Arbitrary
|
405
|
+
|
406
|
+
def self.arbitrary
|
407
|
+
...
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
If you need to define a random proc which returns a object in
|
412
|
+
YourClass, you have to include Coarbitrary, also.
|
413
|
+
|
414
|
+
class YourClass
|
415
|
+
extend Arbitrary
|
416
|
+
include Coarbitrary
|
417
|
+
|
418
|
+
def self.arbitrary
|
419
|
+
...
|
420
|
+
end
|
421
|
+
|
422
|
+
def coarbitrary(g)
|
423
|
+
...
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
However, because it is little complecated to implement both
|
428
|
+
arbitrary and coarbitrary, let's focus how to implement arbitrary
|
429
|
+
first.
|
430
|
+
|
431
|
+
Let's consider the first example:
|
432
|
+
|
433
|
+
class Candy
|
434
|
+
|
435
|
+
def initialize(name, price)
|
436
|
+
@name, @price = name, price
|
437
|
+
end
|
438
|
+
|
439
|
+
def foo
|
440
|
+
...
|
441
|
+
end
|
442
|
+
|
443
|
+
def bar
|
444
|
+
...
|
445
|
+
end
|
446
|
+
|
447
|
+
end
|
448
|
+
|
449
|
+
To write random generator, we have to look up 'initialize'.
|
450
|
+
Here, assume that @name belongs String and @price belongs Integer.
|
451
|
+
|
452
|
+
One simple random generator for Candy:
|
453
|
+
|
454
|
+
(1) using Gen#bind and Gen.unit
|
455
|
+
class Candy
|
456
|
+
extend Arbitrary
|
457
|
+
|
458
|
+
def self.arbitrary
|
459
|
+
Integer.arbitrary.bind do |name|
|
460
|
+
String.arbitrary.bind do |price|
|
461
|
+
Gen.unit(new(name, price))
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
Puzzled? OK, they can be readed as follows:
|
468
|
+
|
469
|
+
take a random (arbitrary) integer and call it 'name'
|
470
|
+
take a random string and call it 'price'
|
471
|
+
return a Gen object which has a new Candy(name, price)
|
472
|
+
|
473
|
+
In general, the class method arbitrary should return a Gen object.
|
474
|
+
Check also gen.rb in RushCheck. There are several combinators to
|
475
|
+
create random generators.
|
476
|
+
|
477
|
+
There are several way to implement random generators. The next
|
478
|
+
example is to use Gen.new without bind and unit.
|
479
|
+
|
480
|
+
(2) using Gen.new
|
481
|
+
class Candy
|
482
|
+
extend Arbitrary
|
483
|
+
|
484
|
+
def self.arbitrary
|
485
|
+
Gen.new do |n, r|
|
486
|
+
r2 = r
|
487
|
+
name, price = [Integer, String].map do |c|
|
488
|
+
r1, r2 = r2.split
|
489
|
+
c.arbitrary.value(n. r1)
|
490
|
+
end
|
491
|
+
new(name, price)
|
492
|
+
end
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
This pattern is useful if your class has many valiables for
|
497
|
+
initialize. Because binding patterns needs much depth of method call
|
498
|
+
chains, it may be failed. This technique is used to avoid the stack
|
499
|
+
overflow. This is one difference between Haskell and Ruby.
|
500
|
+
|
501
|
+
The implementation can be understanded as follows:
|
502
|
+
self.arbitrary returns a new Gen object.
|
503
|
+
let n be an integer and r be a random generator.
|
504
|
+
let r2 equal r
|
505
|
+
name and price are both random objects where
|
506
|
+
get new two random generator r1 and r2 from old r2
|
507
|
+
assign a random value by generating 'arbitrary.value(n, r1)'
|
508
|
+
and discard the random generator r1
|
509
|
+
|
510
|
+
Note that we need new random generator for each random object.
|
511
|
+
Here we create new random generator by spliting. If you use same
|
512
|
+
random generator for generating different objects, then the
|
513
|
+
distribution of objects are same (not generated randomly).
|
514
|
+
|
515
|
+
Because sometimes the initialize of YourClass is complecated,
|
516
|
+
then the random generator self.arbitrary turns to be complecated
|
517
|
+
also.
|
518
|
+
|
519
|
+
In next sections, I will introduce several generators in gen.rb.
|
520
|
+
They may be useful to create your own random genrator. See also rdoc
|
521
|
+
of Gen.
|
522
|
+
|
523
|
+
6.2 Gen.choose
|
524
|
+
|
525
|
+
Gen.choose(lo, hi) returns a Gen object which generates a random
|
526
|
+
value in the bound.
|
527
|
+
|
528
|
+
example.
|
529
|
+
Gen.choose(0, 10) # a generator of Integer in (0..10)
|
530
|
+
|
531
|
+
6.3 Gen.frequency
|
532
|
+
|
533
|
+
Gen.frequency requires an array of pair of Integer and Gen objects,
|
534
|
+
and returns a Gen object. Gen.frequency is used to define the
|
535
|
+
distribution of randomness.
|
536
|
+
|
537
|
+
example.
|
538
|
+
Gen.frequency([[3, Integer.arbitrary], [7, String.arbitrary]])
|
539
|
+
# return a random integer or a random string (by choosing
|
540
|
+
# randomly) Integer:30% String: 70%
|
541
|
+
|
542
|
+
See also SpecialString.
|
543
|
+
|
544
|
+
6.4 Gen.lift_array
|
545
|
+
(I don't like the name and may be changed in future)
|
546
|
+
|
547
|
+
Gen.lift_array takes an array and a block which has a variable.
|
548
|
+
The block should return a Gen object. lift_array returns a Gen
|
549
|
+
object which generates an array of the result of given block for
|
550
|
+
applying each member of given array.
|
551
|
+
|
552
|
+
example.
|
553
|
+
|
554
|
+
class Candy
|
555
|
+
extend Arbitrary
|
556
|
+
|
557
|
+
def self.arbitrary
|
558
|
+
Gen.lift_array([Integer, String]) do |c|
|
559
|
+
c.arbitrary
|
560
|
+
end.bind do |args|
|
561
|
+
new(*args)
|
562
|
+
end
|
563
|
+
end
|
564
|
+
end
|
565
|
+
|
566
|
+
6.5 Gen.oneof
|
567
|
+
|
568
|
+
Gen.oneof requires an array of Gen objects returns a Gen object.
|
569
|
+
|
570
|
+
example.
|
571
|
+
Gen.oneof([Integer.arbitrary, String.arbitrary])
|
572
|
+
# return a random integer or a random string (by choosing
|
573
|
+
# randomly)
|
574
|
+
|
575
|
+
6.6 Gen.promote
|
576
|
+
|
577
|
+
Gen.promote is used to generate a random Proc object.
|
578
|
+
See also proc.rb in RushCheck
|
579
|
+
|
580
|
+
6.7 Gen.rand
|
581
|
+
|
582
|
+
Gen.rand is a random number generator.
|
583
|
+
|
584
|
+
6.8 Gen.sized
|
585
|
+
|
586
|
+
Gen.sized is used to change the size of random object.
|
587
|
+
See also some implementation in RushCheck (grep the source!)
|
588
|
+
|
589
|
+
6.9 Gen.unit
|
590
|
+
|
591
|
+
return Gen object.
|
592
|
+
|
593
|
+
6.10 Gen.vector
|
594
|
+
|
595
|
+
get a vector of Gen.
|
596
|
+
|
597
|
+
Gen.vector(Integer, 3) # => Gen [Integer, Integer, Integer]
|
598
|
+
|
599
|
+
7. Further information
|
600
|
+
|
601
|
+
Webpage should have another useful information:
|
602
|
+
http://rushcheck.rubyforge.org/
|
603
|
+
The project page has a bug tracker and webboard.
|
604
|
+
http://rubyforge.org/projects/rushcheck/
|
605
|
+
|
606
|
+
There is no mailing list for RushCheck (now at 2006-08-08),
|
607
|
+
but don't hesitate to contact to the author!
|
608
|
+
|
609
|
+
Happy hacking and testing!
|
610
|
+
|
611
|
+
|
612
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# malformed format string bug for sprintf
|
2
|
+
# see also a recent news
|
3
|
+
# http://k-tai.impress.co.jp/cda/article/news_toppage/30484.html
|
4
|
+
# this news tells that a japanese handy-phone hungs when it receives
|
5
|
+
# an email which contains the special format string "%s".
|
6
|
+
|
7
|
+
require 'rushcheck/rushcheck'
|
8
|
+
|
9
|
+
def malformed_format_string
|
10
|
+
Assertion.new(String) { |s|
|
11
|
+
sprintf(s)
|
12
|
+
true
|
13
|
+
}.check
|
14
|
+
end
|
15
|
+
|
16
|
+
def malformed_format_string2
|
17
|
+
# SpecialString is used to find special format more likely
|
18
|
+
Assertion.new(SpecialString) { |s|
|
19
|
+
sprintf(s)
|
20
|
+
true
|
21
|
+
}.check
|
22
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# proc.rb
|
2
|
+
# a sample of RushCheck
|
3
|
+
|
4
|
+
require 'rushcheck/rushcheck'
|
5
|
+
|
6
|
+
class Proc
|
7
|
+
# application
|
8
|
+
def **(other)
|
9
|
+
Proc.new do |*args|
|
10
|
+
res = other.call(*args)
|
11
|
+
call(*res)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class MyRandomProc < RandomProc; end
|
17
|
+
|
18
|
+
def associativity_integer
|
19
|
+
MyRandomProc.set_pattern([Integer], [Integer])
|
20
|
+
Assertion.new(MyRandomProc, MyRandomProc, MyRandomProc, Integer) do
|
21
|
+
|a, b, c, x|
|
22
|
+
(a ** (b ** c)).call(x) == ((a ** b) ** c).call(x)
|
23
|
+
end.check
|
24
|
+
end
|
25
|
+
|
26
|
+
# this test takes much time than associativity_integer
|
27
|
+
def associativity_string
|
28
|
+
MyRandomProc.set_pattern([String], [String])
|
29
|
+
Assertion.new(MyRandomProc, MyRandomProc, MyRandomProc, String) do
|
30
|
+
|a, b, c, x|
|
31
|
+
(a ** (b ** c)).call(x) == ((a ** b) ** c).call(x)
|
32
|
+
end.check
|
33
|
+
end
|