rufus-decision 1.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.txt +15 -1
- data/README.txt +82 -49
- data/bin/rufus_decide +149 -0
- data/lib/rufus/decision.rb +295 -283
- data/lib/rufus/hashes.rb +56 -60
- data/test/decision_0_test.rb +120 -162
- data/test/decision_1_test.rb +60 -32
- data/test/eval_test.rb +6 -14
- data/test/goal.csv +3 -0
- data/test/input.csv +3 -0
- data/test/table.csv +9 -0
- data/test/test.rb +7 -3
- data/test/test_base.rb +51 -0
- data/test/transpose_test.rb +64 -0
- metadata +14 -8
- data/test/dmixin.rb +0 -55
data/lib/rufus/hashes.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
-
#
|
2
1
|
#--
|
3
|
-
# Copyright (c) 2008, John Mettraux, jmettraux@gmail.com
|
2
|
+
# Copyright (c) 2008-2009, John Mettraux, jmettraux@gmail.com
|
4
3
|
#
|
5
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
5
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -19,19 +18,17 @@
|
|
19
18
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
19
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
20
|
# THE SOFTWARE.
|
22
|
-
#++
|
23
21
|
#
|
22
|
+
# Made in Japan.
|
23
|
+
#++
|
24
24
|
|
25
|
-
#
|
26
|
-
# "made in Japan"
|
27
|
-
#
|
28
|
-
# John Mettraux at openwfe.org
|
29
|
-
#
|
30
25
|
|
26
|
+
require 'fileutils'
|
31
27
|
require 'rufus/treechecker'
|
32
28
|
|
33
29
|
|
34
30
|
module Rufus
|
31
|
+
module Decision
|
35
32
|
|
36
33
|
#
|
37
34
|
# In the Java world, this class would be considered abstract.
|
@@ -46,6 +43,8 @@ module Rufus
|
|
46
43
|
#
|
47
44
|
class HashFilter
|
48
45
|
|
46
|
+
attr_reader :parent_hash
|
47
|
+
|
49
48
|
def initialize (parent_hash)
|
50
49
|
|
51
50
|
@parent_hash = parent_hash
|
@@ -67,59 +66,59 @@ module Rufus
|
|
67
66
|
|
68
67
|
protected
|
69
68
|
|
70
|
-
|
69
|
+
def do_split (element)
|
71
70
|
|
72
|
-
|
71
|
+
return [ nil, element ] unless element.is_a?(String)
|
73
72
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
73
|
+
a = element.split(':', 2)
|
74
|
+
return [ nil ] + a if a.length == 1
|
75
|
+
a
|
76
|
+
end
|
78
77
|
|
79
|
-
|
78
|
+
def handles_prefix? (p)
|
80
79
|
|
81
|
-
|
82
|
-
|
80
|
+
false
|
81
|
+
end
|
83
82
|
|
84
|
-
|
83
|
+
def do_eval (key, p, k)
|
85
84
|
|
86
|
-
|
87
|
-
|
88
|
-
|
85
|
+
raise NotImplementedError.new(
|
86
|
+
"missing do_eval(key, p, k) implementation")
|
87
|
+
end
|
89
88
|
|
90
|
-
|
89
|
+
def do_lookup (key, p, k)
|
91
90
|
|
92
|
-
|
91
|
+
if handles_prefix?(p)
|
93
92
|
|
94
|
-
|
93
|
+
do_eval(key, p, k)
|
95
94
|
|
96
|
-
|
95
|
+
elsif @parent_hash.respond_to?(:do_lookup)
|
97
96
|
|
98
|
-
|
97
|
+
@parent_hash.do_lookup(key, p, k)
|
99
98
|
|
100
|
-
|
99
|
+
else
|
101
100
|
|
102
|
-
|
103
|
-
end
|
101
|
+
@parent_hash[key]
|
104
102
|
end
|
103
|
+
end
|
105
104
|
|
106
|
-
|
105
|
+
def do_put (key, value, p, v)
|
107
106
|
|
108
|
-
|
107
|
+
val = value
|
109
108
|
|
110
|
-
|
109
|
+
if handles_prefix?(p)
|
111
110
|
|
112
|
-
|
111
|
+
@parent_hash[key] = do_eval(value, p, v)
|
113
112
|
|
114
|
-
|
113
|
+
elsif @parent_hash.respond_to?(:do_put)
|
115
114
|
|
116
|
-
|
115
|
+
@parent_hash.do_put key, value, p, v
|
117
116
|
|
118
|
-
|
117
|
+
else
|
119
118
|
|
120
|
-
|
121
|
-
end
|
119
|
+
@parent_hash[key] = value
|
122
120
|
end
|
121
|
+
end
|
123
122
|
end
|
124
123
|
|
125
124
|
#
|
@@ -146,33 +145,33 @@ module Rufus
|
|
146
145
|
|
147
146
|
def initialize (parent_hash)
|
148
147
|
|
149
|
-
super
|
148
|
+
super(parent_hash)
|
150
149
|
end
|
151
150
|
|
152
151
|
protected
|
153
152
|
|
154
|
-
|
153
|
+
RP = %w{ r ruby reval }
|
155
154
|
|
156
|
-
|
155
|
+
def handles_prefix? (prefix)
|
157
156
|
|
158
|
-
|
159
|
-
|
157
|
+
RP.include?(prefix)
|
158
|
+
end
|
160
159
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
def get_binding
|
160
|
+
# Ready for override.
|
161
|
+
#
|
162
|
+
def get_binding
|
165
163
|
|
166
|
-
|
167
|
-
|
164
|
+
binding()
|
165
|
+
end
|
168
166
|
|
169
|
-
|
167
|
+
def do_eval (key, p, k)
|
170
168
|
|
171
|
-
|
172
|
-
|
173
|
-
end
|
169
|
+
Rufus::Decision.check_and_eval(k, get_binding)
|
170
|
+
end
|
174
171
|
end
|
175
172
|
|
173
|
+
# An abstract syntax tree check (prior to any ruby eval)
|
174
|
+
#
|
176
175
|
TREECHECKER = Rufus::TreeChecker.new do
|
177
176
|
|
178
177
|
exclude_fvccall :abort, :exit, :exit!
|
@@ -200,6 +199,9 @@ module Rufus
|
|
200
199
|
end
|
201
200
|
TREECHECKER.freeze
|
202
201
|
|
202
|
+
# Given a string (of ruby code), first makes sure it doesn't contain
|
203
|
+
# harmful code, then evaluates it.
|
204
|
+
#
|
203
205
|
def self.check_and_eval (ruby_code, bndng=binding())
|
204
206
|
|
205
207
|
TREECHECKER.check(ruby_code)
|
@@ -209,12 +211,6 @@ module Rufus
|
|
209
211
|
eval(ruby_code, bndng)
|
210
212
|
end
|
211
213
|
|
212
|
-
|
213
|
-
|
214
|
-
def Rufus.unescape (text)
|
215
|
-
|
216
|
-
text.gsub("\\\\\\$\\{", "\\${")
|
217
|
-
end
|
218
|
-
|
214
|
+
end
|
219
215
|
end
|
220
216
|
|
data/test/decision_0_test.rb
CHANGED
@@ -1,37 +1,28 @@
|
|
1
1
|
|
2
2
|
#
|
3
|
-
# Testing rufus-
|
4
|
-
#
|
5
|
-
# John Mettraux at openwfe.org
|
3
|
+
# Testing rufus-decision
|
6
4
|
#
|
7
5
|
# Sun Oct 29 15:41:44 JST 2006
|
8
6
|
#
|
9
7
|
|
10
8
|
require 'test/unit'
|
11
9
|
|
12
|
-
require '
|
10
|
+
require File.dirname(__FILE__) + '/test_base.rb'
|
13
11
|
|
14
12
|
|
15
|
-
class
|
13
|
+
class Decision0Test < Test::Unit::TestCase
|
16
14
|
include DecisionTestMixin
|
17
15
|
|
18
|
-
|
19
|
-
#end
|
20
|
-
|
21
|
-
#def teardown
|
22
|
-
#end
|
23
|
-
|
24
|
-
CSV0 = \
|
25
|
-
"""
|
16
|
+
CSV0 = %{
|
26
17
|
,,
|
27
18
|
in:fx,in:fy,out:fz
|
28
19
|
,,
|
29
20
|
a,b,0
|
30
21
|
c,d,1
|
31
22
|
e,f,2
|
32
|
-
|
23
|
+
}
|
33
24
|
|
34
|
-
def
|
25
|
+
def test_0
|
35
26
|
|
36
27
|
wi = {
|
37
28
|
"fx" => "c",
|
@@ -46,15 +37,13 @@ e,f,2
|
|
46
37
|
do_test(CSV0, wi, {}, { "fz" => nil }, false)
|
47
38
|
end
|
48
39
|
|
49
|
-
CSV0B =
|
50
|
-
"""
|
51
|
-
|
40
|
+
CSV0B = %{
|
52
41
|
in:fx,in:fy,out:fz
|
53
42
|
|
54
43
|
a,b,0
|
55
44
|
c,d,1
|
56
45
|
e,f,2
|
57
|
-
|
46
|
+
}
|
58
47
|
|
59
48
|
def test_0b
|
60
49
|
|
@@ -67,32 +56,24 @@ e,f,2
|
|
67
56
|
|
68
57
|
# test 1 moved to decision_1_test.rb
|
69
58
|
|
70
|
-
CSV2 =
|
71
|
-
"""
|
59
|
+
CSV2 = %{
|
72
60
|
in:fx, in:fy, out:fz
|
73
61
|
,,
|
74
62
|
a, b, 0
|
75
63
|
c, d, 1
|
76
64
|
e, f, 2
|
77
|
-
|
65
|
+
}
|
78
66
|
|
79
67
|
def test_2
|
80
68
|
|
81
|
-
wi = {
|
82
|
-
"fx" => "c",
|
83
|
-
"fy" => "d"
|
84
|
-
}
|
69
|
+
wi = { "fx" => "c", "fy" => "d" }
|
85
70
|
do_test(CSV2, wi, {}, { "fz" => "1" }, false)
|
86
71
|
|
87
|
-
wi = {
|
88
|
-
"fx" => "a",
|
89
|
-
"fy" => "d"
|
90
|
-
}
|
72
|
+
wi = { "fx" => "a", "fy" => "d" }
|
91
73
|
do_test(CSV2, wi, {}, { "fz" => nil }, false)
|
92
74
|
end
|
93
75
|
|
94
|
-
CSV3 =
|
95
|
-
"""
|
76
|
+
CSV3 = %{
|
96
77
|
in:weather, in:month, out:take_umbrella?
|
97
78
|
,,
|
98
79
|
raining, , yes
|
@@ -100,7 +81,7 @@ sunny, , no
|
|
100
81
|
cloudy, june, yes
|
101
82
|
cloudy, may, yes
|
102
83
|
cloudy, , no
|
103
|
-
|
84
|
+
}
|
104
85
|
|
105
86
|
def test_3
|
106
87
|
|
@@ -143,24 +124,24 @@ cloudy, , no
|
|
143
124
|
|
144
125
|
def test_3c
|
145
126
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
127
|
+
table = Rufus::DecisionTable.new(%{
|
128
|
+
in:topic,in:region,out:team_member
|
129
|
+
sports,europe,Alice
|
130
|
+
sports,,Bob
|
131
|
+
finance,america,Charly
|
132
|
+
finance,europe,Donald
|
133
|
+
finance,,Ernest
|
134
|
+
politics,asia,Fujio
|
135
|
+
politics,america,Gilbert
|
136
|
+
politics,,Henry
|
137
|
+
,,Zach
|
138
|
+
})
|
139
|
+
|
140
|
+
h = {}
|
141
|
+
h["topic"] = "politics"
|
142
|
+
table.transform! h
|
143
|
+
|
144
|
+
assert_equal "Henry", h["team_member"]
|
164
145
|
end
|
165
146
|
|
166
147
|
CSV3D = "http://spreadsheets.google.com/pub?key=pCkopoeZwCNsMWOVeDjR1TQ&output=csv&gid=0"
|
@@ -194,95 +175,83 @@ cloudy, , no
|
|
194
175
|
|
195
176
|
def test_3e
|
196
177
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
178
|
+
table = Rufus::DecisionTable.new(%{
|
179
|
+
in:topic,in:region,out:team_member
|
180
|
+
sports,europe,Alice
|
181
|
+
})
|
201
182
|
|
202
|
-
|
203
|
-
|
204
|
-
|
183
|
+
h0 = {}
|
184
|
+
h0["topic"] = "politics"
|
185
|
+
h1 = table.transform! h0
|
205
186
|
|
206
|
-
|
187
|
+
assert_equal h0.object_id, h1.object_id
|
207
188
|
|
208
|
-
|
209
|
-
|
210
|
-
|
189
|
+
h0 = {}
|
190
|
+
h0["topic"] = "politics"
|
191
|
+
h1 = table.transform h0
|
211
192
|
|
212
|
-
|
193
|
+
assert_not_equal h0.object_id, h1.object_id
|
213
194
|
end
|
214
195
|
|
215
|
-
# CSV4 = \
|
216
|
-
#'''
|
217
|
-
#"in:weather", "in:month", "out:take_umbrella?"
|
218
|
-
#"","",""
|
219
|
-
#"raining","","yes"
|
220
|
-
#"sunny","","no"
|
221
|
-
#"cloudy","june","yes"
|
222
|
-
#"cloudy","may","yes"
|
223
|
-
#"cloudy","","no"
|
224
|
-
#'''
|
225
|
-
|
226
|
-
#def test_4
|
227
|
-
# h = {}
|
228
|
-
# h["weather"] = "raining"
|
229
|
-
# h["month"] = "december"
|
230
|
-
# do_test(CSV4, h, { "take_umbrella?" => "yes" }, false)
|
231
|
-
# h = {}
|
232
|
-
# h["weather"] = "cloudy"
|
233
|
-
# h["month"] = "june"
|
234
|
-
# do_test(CSV4, h, { "take_umbrella?" => "yes" }, false)
|
235
|
-
# h = {}
|
236
|
-
# h["weather"] = "cloudy"
|
237
|
-
# h["month"] = "march"
|
238
|
-
# do_test(CSV4, h, { "take_umbrella?" => "no" }, false)
|
239
|
-
#end
|
240
|
-
|
241
|
-
CSV5 = \
|
242
|
-
"""
|
243
|
-
through,ignorecase,,
|
244
|
-
,,,
|
245
|
-
in:fx, in:fy, out:fX, out:fY
|
246
|
-
,,,
|
247
|
-
a, , true,
|
248
|
-
, a, , true
|
249
|
-
b, , false,
|
250
|
-
, b, , false
|
251
|
-
"""
|
252
196
|
|
253
|
-
def
|
197
|
+
def test_through_and_ignorecase
|
254
198
|
|
255
|
-
|
256
|
-
|
257
|
-
|
199
|
+
table = %{
|
200
|
+
through,ignorecase,,
|
201
|
+
,,,
|
202
|
+
in:fx, in:fy, out:fX, out:fY
|
203
|
+
,,,
|
204
|
+
a, , true,
|
205
|
+
, a, , true
|
206
|
+
b, , false,
|
207
|
+
, b, , false
|
258
208
|
}
|
259
|
-
do_test(CSV5, wi, {}, { "fX" => "true", "fY" => "true" }, false)
|
260
209
|
|
261
|
-
wi = {
|
262
|
-
|
263
|
-
"fy" => "b"
|
264
|
-
}
|
265
|
-
do_test(CSV5, wi, {}, { "fX" => "true", "fY" => "false" }, false)
|
210
|
+
wi = { 'fx' => 'a', 'fy' => 'a' }
|
211
|
+
do_test(table, wi, {}, { 'fX' => 'true', 'fY' => 'true' }, false)
|
266
212
|
|
267
|
-
wi = {
|
268
|
-
|
269
|
-
|
213
|
+
wi = { 'fx' => 'a', 'fy' => 'b' }
|
214
|
+
do_test(table, wi, {}, { 'fX' => 'true', 'fY' => 'false' }, false)
|
215
|
+
|
216
|
+
wi = { 'fx' => 'A', 'fy' => 'b' }
|
217
|
+
do_test(table, wi, {}, { 'fX' => 'true', 'fY' => 'false' }, false)
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_through_and_ignorecase_set_at_table_initialization
|
221
|
+
|
222
|
+
table = %{
|
223
|
+
in:fx, in:fy, out:fX, out:fY
|
224
|
+
,,,
|
225
|
+
a, , true,
|
226
|
+
, a, , true
|
227
|
+
b, , false,
|
228
|
+
, b, , false
|
270
229
|
}
|
271
|
-
|
230
|
+
|
231
|
+
table = Rufus::Decision::Table.new(
|
232
|
+
table, :through => true, :ignore_case => true)
|
233
|
+
|
234
|
+
wi = { 'fx' => 'a', 'fy' => 'a' }
|
235
|
+
do_test(table, wi, {}, { 'fX' => 'true', 'fY' => 'true' }, false)
|
236
|
+
|
237
|
+
wi = { 'fx' => 'a', 'fy' => 'b' }
|
238
|
+
do_test(table, wi, {}, { 'fX' => 'true', 'fY' => 'false' }, false)
|
239
|
+
|
240
|
+
wi = { 'fx' => 'A', 'fy' => 'b' }
|
241
|
+
do_test(table, wi, {}, { 'fX' => 'true', 'fY' => 'false' }, false)
|
272
242
|
end
|
273
243
|
|
274
244
|
#
|
275
245
|
# TEST 6
|
276
246
|
|
277
|
-
CSV6 =
|
278
|
-
"""
|
247
|
+
CSV6 = %{
|
279
248
|
,
|
280
249
|
in:fx, out:fy
|
281
250
|
,
|
282
251
|
<10,a
|
283
252
|
<=100,b
|
284
253
|
,c
|
285
|
-
|
254
|
+
}
|
286
255
|
|
287
256
|
def test_6
|
288
257
|
|
@@ -300,15 +269,14 @@ in:fx, out:fy
|
|
300
269
|
#
|
301
270
|
# TEST 7
|
302
271
|
|
303
|
-
CSV7 =
|
304
|
-
"""
|
272
|
+
CSV7 = %{
|
305
273
|
,
|
306
274
|
in:fx, out:fy
|
307
275
|
,
|
308
276
|
>100,a
|
309
277
|
>=10,b
|
310
278
|
,c
|
311
|
-
|
279
|
+
}
|
312
280
|
|
313
281
|
def test_7
|
314
282
|
|
@@ -328,42 +296,44 @@ in:fx, out:fy
|
|
328
296
|
do_test(CSV7, wi, {}, { "fy" => "a" }, false)
|
329
297
|
end
|
330
298
|
|
331
|
-
CSV8 =
|
332
|
-
"""
|
299
|
+
CSV8 = %{
|
333
300
|
in:efx,in:efy,out:efz
|
334
301
|
a,b,0
|
335
302
|
c,d,1
|
336
303
|
e,f,2
|
337
|
-
|
304
|
+
}
|
338
305
|
|
339
306
|
def test_8
|
340
307
|
|
341
308
|
assert_equal CSV8.strip, Rufus::DecisionTable.new(CSV8).to_csv.strip
|
342
309
|
end
|
343
310
|
|
344
|
-
CSV9 =
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
"""
|
311
|
+
CSV9 = %{
|
312
|
+
in:fx,in:fy,out:fz
|
313
|
+
a,b,0
|
314
|
+
c,d,${r: 1 + 2}
|
315
|
+
e,f,2
|
316
|
+
}
|
351
317
|
|
352
|
-
def
|
318
|
+
def test_ruby_eval
|
353
319
|
|
354
|
-
wi = {
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
320
|
+
wi = { 'fx' => 'c', 'fy' => 'd' }
|
321
|
+
do_test(CSV9, wi, { :ruby_eval => true }, { 'fz' => '3' }, false)
|
322
|
+
end
|
323
|
+
|
324
|
+
def test_ruby_eval_set_at_table_initialization
|
325
|
+
|
326
|
+
table = Rufus::Decision::Table.new(CSV9, :ruby_eval => true)
|
327
|
+
|
328
|
+
wi = { 'fx' => 'c', 'fy' => 'd' }
|
329
|
+
do_test(table, wi, {}, { 'fz' => '3' }, false)
|
359
330
|
end
|
360
331
|
|
361
|
-
CSV10 =
|
362
|
-
"""
|
332
|
+
CSV10 = %{
|
363
333
|
in:fx,in:fx,out:fz
|
364
334
|
>90,<92,ok
|
365
335
|
,,bad
|
366
|
-
|
336
|
+
}
|
367
337
|
|
368
338
|
def test_10
|
369
339
|
|
@@ -377,11 +347,7 @@ in:fx,in:fx,out:fz
|
|
377
347
|
do_test(CSV10, wi, {}, { "fz" => "bad" }, false)
|
378
348
|
end
|
379
349
|
|
380
|
-
|
381
|
-
# Fu Zhang's test case
|
382
|
-
|
383
|
-
CSV11 = \
|
384
|
-
'''
|
350
|
+
CSV11 = %{
|
385
351
|
through
|
386
352
|
in:f1,in:f1,in:f2,in:f3,out:o1,out:e1,out:e2
|
387
353
|
|
@@ -390,19 +356,15 @@ in:f1,in:f1,in:f2,in:f3,out:o1,out:e1,out:e2
|
|
390
356
|
<100,>=95,>2.0,,"Expection1",,
|
391
357
|
<100,>=95,,>2.0,,,"Expection2"
|
392
358
|
<100,>=95,,>2.0,"Invalid",,
|
393
|
-
|
359
|
+
}
|
394
360
|
|
395
|
-
def
|
361
|
+
def test_fu_zhang
|
396
362
|
|
397
|
-
wi = {
|
398
|
-
do_test CSV11, wi, {}, {
|
363
|
+
wi = { 'f1' => 97, 'f2' => 5 }
|
364
|
+
do_test CSV11, wi, {}, { 'o1' => 'Expection1' }, false
|
399
365
|
end
|
400
366
|
|
401
|
-
|
402
|
-
# 'accumulate'
|
403
|
-
|
404
|
-
CSV12 = \
|
405
|
-
"""
|
367
|
+
CSV12 = %{
|
406
368
|
accumulate
|
407
369
|
,,,
|
408
370
|
in:fx, in:fy, out:fX, out:fY
|
@@ -411,9 +373,9 @@ a, , red, green
|
|
411
373
|
, a, blue, purple
|
412
374
|
b, , yellow, beige
|
413
375
|
, b, white, kuro
|
414
|
-
|
376
|
+
}
|
415
377
|
|
416
|
-
def
|
378
|
+
def test_accumulate
|
417
379
|
|
418
380
|
wi = { "fx" => "a", "fy" => "a" }
|
419
381
|
do_test CSV12, wi, {}, { "fX" => "red;blue", "fY" => "green;purple" }, false
|
@@ -453,17 +415,13 @@ b, , yellow, beige
|
|
453
415
|
assert (not r.include?("7"))
|
454
416
|
end
|
455
417
|
|
456
|
-
|
457
|
-
# Testing ranges
|
458
|
-
|
459
|
-
CSV13 = \
|
460
|
-
"""
|
418
|
+
CSV13 = %{
|
461
419
|
in:fx,out:fz
|
462
420
|
90..92,ok
|
463
421
|
,bad
|
464
|
-
|
422
|
+
}
|
465
423
|
|
466
|
-
def
|
424
|
+
def test_range
|
467
425
|
|
468
426
|
wi = { "fx" => "91" }
|
469
427
|
do_test CSV13, wi, {}, { "fz" => "ok" }, false
|