roma 0.8.2
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/LICENSE.rdoc +675 -0
- data/README.rdoc +0 -0
- data/Rakefile +70 -0
- data/bin/mkrecent +7 -0
- data/bin/mkroute +7 -0
- data/bin/recoverlost +8 -0
- data/bin/recoverlost_alist +8 -0
- data/bin/romad +7 -0
- data/bin/sample_watcher +8 -0
- data/bin/sample_watcher2 +8 -0
- data/bin/simple_bench +8 -0
- data/bin/ssroute +7 -0
- data/bin/tribunus +7 -0
- data/lib/roma/async_process.rb +696 -0
- data/lib/roma/command/bg_command_receiver.rb +188 -0
- data/lib/roma/command/mh_command_receiver.rb +117 -0
- data/lib/roma/command/receiver.rb +287 -0
- data/lib/roma/command/rt_command_receiver.rb +147 -0
- data/lib/roma/command/st_command_receiver.rb +564 -0
- data/lib/roma/command/util_command_receiver.rb +67 -0
- data/lib/roma/command/vn_command_receiver.rb +143 -0
- data/lib/roma/command_plugin.rb +11 -0
- data/lib/roma/config.rb +64 -0
- data/lib/roma/event/con_pool.rb +140 -0
- data/lib/roma/event/handler.rb +159 -0
- data/lib/roma/plugin/plugin_alist.rb +1572 -0
- data/lib/roma/plugin/plugin_debug.rb +19 -0
- data/lib/roma/plugin/plugin_test.rb +14 -0
- data/lib/roma/romad.rb +582 -0
- data/lib/roma/routing/cb_rttable.rb +326 -0
- data/lib/roma/routing/merkle_tree.rb +54 -0
- data/lib/roma/routing/rttable.rb +148 -0
- data/lib/roma/stats.rb +112 -0
- data/lib/roma/storage/basic_storage.rb +510 -0
- data/lib/roma/storage/dbm_storage.rb +80 -0
- data/lib/roma/storage/dummy_storage.rb +44 -0
- data/lib/roma/storage/rh_storage.rb +35 -0
- data/lib/roma/storage/sqlite3_storage.rb +73 -0
- data/lib/roma/storage/tc_storage.rb +133 -0
- data/lib/roma/tools/mkrecent.rb +138 -0
- data/lib/roma/tools/mkroute.rb +52 -0
- data/lib/roma/tools/recoverlost.rb +9 -0
- data/lib/roma/tools/recoverlost_alist.rb +9 -0
- data/lib/roma/tools/recoverlost_lib.rb +217 -0
- data/lib/roma/tools/sample_watcher.rb +38 -0
- data/lib/roma/tools/sample_watcher2.rb +38 -0
- data/lib/roma/tools/simple_bench.rb +57 -0
- data/lib/roma/tools/ssroute.rb +23 -0
- data/lib/roma/tools/tribunus.rb +299 -0
- data/lib/roma/version.rb +4 -0
- data/lib/roma/write_behind.rb +179 -0
- data/test/rcirb.rb +16 -0
- data/test/roma-test-utils.rb +65 -0
- data/test/run-test.rb +16 -0
- data/test/t_cpdata.rb +277 -0
- data/test/t_listplugin.rb +592 -0
- data/test/t_rclient.rb +318 -0
- data/test/t_routing_data.rb +100 -0
- data/test/t_storage.rb +644 -0
- data/test/t_writebehind.rb +200 -0
- metadata +134 -0
data/test/t_rclient.rb
ADDED
@@ -0,0 +1,318 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
require 'roma/client/rclient'
|
5
|
+
|
6
|
+
Roma::Client::RomaClient.class_eval{
|
7
|
+
def init_sync_routing_proc
|
8
|
+
end
|
9
|
+
}
|
10
|
+
|
11
|
+
class RClientTest < Test::Unit::TestCase
|
12
|
+
include RomaTestUtils
|
13
|
+
|
14
|
+
def setup
|
15
|
+
start_roma
|
16
|
+
@rc=Roma::Client::RomaClient.new(["localhost_11211","localhost_11212"])
|
17
|
+
end
|
18
|
+
|
19
|
+
def teardown
|
20
|
+
stop_roma
|
21
|
+
Roma::Messaging::ConPool::instance.close_all
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_set_get_delete
|
25
|
+
@rc.delete("abc")
|
26
|
+
assert_nil( @rc.get("abc") )
|
27
|
+
assert_equal("STORED", @rc.set("abc","value abc"))
|
28
|
+
assert_equal("value abc", @rc.get("abc"))
|
29
|
+
assert_equal("STORED", @rc.set("abc","value abc")) # 上書きは成功する
|
30
|
+
assert_equal("DELETED", @rc.delete("abc"))
|
31
|
+
assert_nil( @rc.get("abc"))
|
32
|
+
assert_equal("NOT_FOUND", @rc.delete("abc"))
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_set_get
|
36
|
+
10.times{|i|
|
37
|
+
s = i.to_s * 1024000
|
38
|
+
assert_equal("STORED", @rc.set("abc", s))
|
39
|
+
assert(s == @rc.get("abc"))
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_set_gets
|
44
|
+
keys = []
|
45
|
+
assert_equal(@rc.gets(["key-1","key-2"]).length,0)
|
46
|
+
10.times{|i|
|
47
|
+
assert_equal("STORED", @rc.set("key-#{i}", "value-#{i}"))
|
48
|
+
keys << "key-#{i}"
|
49
|
+
}
|
50
|
+
ret = @rc.gets(keys)
|
51
|
+
assert_equal(ret.length,10)
|
52
|
+
ret.each_pair{|k,v|
|
53
|
+
assert_equal(k[-1],v[-1])
|
54
|
+
assert_equal(k[0..3],"key-")
|
55
|
+
assert_equal(v[0..5],"value-")
|
56
|
+
}
|
57
|
+
keys << "key-99"
|
58
|
+
ret = @rc.gets(keys)
|
59
|
+
assert_equal(ret.length,10)
|
60
|
+
|
61
|
+
assert_equal("DELETED", @rc.delete("key-5"))
|
62
|
+
ret = @rc.gets(keys)
|
63
|
+
assert_equal(ret.length,9)
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_out
|
67
|
+
# 本当に消す
|
68
|
+
@rc.out("key-out")
|
69
|
+
# 本当にない場合は NOT_DELETED
|
70
|
+
assert_equal("NOT_DELETED", @rc.out("key-out"))
|
71
|
+
assert_equal("STORED", @rc.set("key-out","value out"))
|
72
|
+
assert_equal("DELETED", @rc.out("key-out"))
|
73
|
+
assert_equal("STORED", @rc.set("key-out","value out"))
|
74
|
+
# 削除マークをつける
|
75
|
+
assert_equal("DELETED", @rc.delete("key-out"))
|
76
|
+
# delete してもマークを消すので DELETED
|
77
|
+
assert_equal("DELETED", @rc.out("key-out"))
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_add
|
81
|
+
assert_nil( @rc.get("add") )
|
82
|
+
assert_equal("STORED", @rc.add("add","value add"))
|
83
|
+
assert_equal("NOT_STORED", @rc.add("add","value add")) # 上書きは失敗する
|
84
|
+
assert_equal("DELETED", @rc.delete("add"))
|
85
|
+
assert_equal("STORED", @rc.add("add","value add")) # delete 後の add の成功を確認
|
86
|
+
assert_equal("DELETED", @rc.delete("add"))
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_replace
|
90
|
+
assert_nil( @rc.get("replace") )
|
91
|
+
assert_equal("NOT_STORED", @rc.replace("replace","value replace"))
|
92
|
+
assert_nil( @rc.get("replace") )
|
93
|
+
assert_equal("STORED", @rc.add("replace","value add"))
|
94
|
+
assert_equal("STORED", @rc.replace("replace","value replace"))
|
95
|
+
assert_equal("DELETED", @rc.delete("replace"))
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_append
|
99
|
+
assert_nil( @rc.get("append") )
|
100
|
+
assert_equal("NOT_STORED", @rc.append("append","append"))
|
101
|
+
assert_equal("STORED", @rc.set("append","set"))
|
102
|
+
assert_equal("set", @rc.get("append"))
|
103
|
+
assert_equal("STORED", @rc.append("append","append"))
|
104
|
+
assert_equal("setappend", @rc.get("append"))
|
105
|
+
assert_equal("DELETED", @rc.delete("append"))
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_prepend
|
109
|
+
assert_nil( @rc.get("prepend"))
|
110
|
+
assert_equal("NOT_STORED", @rc.prepend("prepend","prepend"))
|
111
|
+
assert_equal("STORED", @rc.set("prepend","set"))
|
112
|
+
assert_equal("set", @rc.get("prepend"))
|
113
|
+
assert_equal("STORED", @rc.prepend("prepend","prepend"))
|
114
|
+
assert_equal("prependset", @rc.get("prepend"))
|
115
|
+
assert_equal("DELETED", @rc.delete("prepend"))
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_incr
|
119
|
+
assert_nil( @rc.get("incr"))
|
120
|
+
assert_equal("NOT_FOUND", @rc.incr("incr"))
|
121
|
+
assert_equal("STORED", @rc.set("incr","100"))
|
122
|
+
assert_equal(101, @rc.incr("incr"))
|
123
|
+
assert_equal(102, @rc.incr("incr"))
|
124
|
+
assert_equal("DELETED", @rc.delete("incr"))
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_decr
|
128
|
+
assert_nil( @rc.get("decr") )
|
129
|
+
assert_equal("NOT_FOUND", @rc.decr("decr"))
|
130
|
+
assert_equal("STORED", @rc.set("decr","100"))
|
131
|
+
assert_equal(99, @rc.decr("decr"))
|
132
|
+
assert_equal(98, @rc.decr("decr"))
|
133
|
+
assert_equal("DELETED", @rc.delete("decr"))
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_createhash
|
137
|
+
con = Roma::Messaging::ConPool.instance.get_connection("localhost_11211")
|
138
|
+
con.write("hashlist\r\n")
|
139
|
+
ret = con.gets
|
140
|
+
assert_equal("roma", ret.chomp )
|
141
|
+
|
142
|
+
con.write("createhash test\r\n")
|
143
|
+
ret = con.gets
|
144
|
+
assert_equal("{\"localhost_11212\"=>\"CREATED\", \"localhost_11211\"=>\"CREATED\"}", ret.chomp )
|
145
|
+
|
146
|
+
con.write("hashlist\r\n")
|
147
|
+
ret = con.gets
|
148
|
+
assert_equal("roma test", ret.chomp )
|
149
|
+
|
150
|
+
assert_equal("STORED", @rc.set("roma","hname=roma"))
|
151
|
+
assert_equal("hname=roma", @rc.get("roma"))
|
152
|
+
@rc.default_hash_name='test'
|
153
|
+
assert_nil( @rc.get("roma") )
|
154
|
+
assert_equal("STORED", @rc.set("roma","hname=test"))
|
155
|
+
assert_equal("hname=test", @rc.get("roma"))
|
156
|
+
@rc.default_hash_name='roma'
|
157
|
+
assert_equal("hname=roma", @rc.get("roma"))
|
158
|
+
assert_equal("DELETED", @rc.delete("roma"))
|
159
|
+
|
160
|
+
@rc.default_hash_name='not_exist_hash' # 存在しないハッシュへのアクセス
|
161
|
+
begin
|
162
|
+
@rc.get("roma")
|
163
|
+
assert(false)
|
164
|
+
rescue =>e
|
165
|
+
assert_equal('SERVER_ERROR not_exist_hash dose not exists.',e.message)
|
166
|
+
end
|
167
|
+
|
168
|
+
begin
|
169
|
+
@rc.set("roma","hname=roma")
|
170
|
+
assert(false)
|
171
|
+
rescue =>e
|
172
|
+
assert_equal('SERVER_ERROR not_exist_hash dose not exists.',e.message)
|
173
|
+
end
|
174
|
+
|
175
|
+
begin
|
176
|
+
@rc.delete("roma")
|
177
|
+
assert(false)
|
178
|
+
rescue =>e
|
179
|
+
assert_equal('SERVER_ERROR not_exist_hash dose not exists.',e.message)
|
180
|
+
end
|
181
|
+
|
182
|
+
begin
|
183
|
+
@rc.add("add","value add")
|
184
|
+
assert(false)
|
185
|
+
rescue =>e
|
186
|
+
assert_equal('SERVER_ERROR not_exist_hash dose not exists.',e.message)
|
187
|
+
end
|
188
|
+
|
189
|
+
begin
|
190
|
+
@rc.replace("replace","value replace")
|
191
|
+
assert(false)
|
192
|
+
rescue =>e
|
193
|
+
assert_equal('SERVER_ERROR not_exist_hash dose not exists.',e.message)
|
194
|
+
end
|
195
|
+
|
196
|
+
begin
|
197
|
+
@rc.append("append","append")
|
198
|
+
assert(false)
|
199
|
+
rescue =>e
|
200
|
+
assert_equal('SERVER_ERROR not_exist_hash dose not exists.', e.message)
|
201
|
+
end
|
202
|
+
|
203
|
+
begin
|
204
|
+
@rc.prepend("prepend","prepend")
|
205
|
+
assert(false)
|
206
|
+
rescue =>e
|
207
|
+
assert_equal('SERVER_ERROR not_exist_hash dose not exists.',e.message)
|
208
|
+
end
|
209
|
+
|
210
|
+
begin
|
211
|
+
@rc.incr("incr")
|
212
|
+
assert(false)
|
213
|
+
rescue =>e
|
214
|
+
assert_equal('SERVER_ERROR not_exist_hash dose not exists.',e.message)
|
215
|
+
end
|
216
|
+
|
217
|
+
begin
|
218
|
+
@rc.decr("decr")
|
219
|
+
assert(false)
|
220
|
+
rescue =>e
|
221
|
+
assert_equal('SERVER_ERROR not_exist_hash dose not exists.',e.message)
|
222
|
+
end
|
223
|
+
|
224
|
+
con.write("deletehash test\r\n")
|
225
|
+
ret = con.gets
|
226
|
+
assert_equal( "{\"localhost_11212\"=>\"DELETED\", \"localhost_11211\"=>\"DELETED\"}", ret.chomp )
|
227
|
+
|
228
|
+
con.close
|
229
|
+
end
|
230
|
+
|
231
|
+
def test_createhash2
|
232
|
+
# test ハッシュを追加し終了する
|
233
|
+
con = Roma::Messaging::ConPool.instance.get_connection("localhost_11211")
|
234
|
+
con.write("hashlist\r\n")
|
235
|
+
ret = con.gets
|
236
|
+
assert_equal("roma", ret.chomp)
|
237
|
+
|
238
|
+
con.write("createhash test\r\n")
|
239
|
+
ret = con.gets
|
240
|
+
assert_equal("{\"localhost_11212\"=>\"CREATED\", \"localhost_11211\"=>\"CREATED\"}", ret.chomp )
|
241
|
+
|
242
|
+
assert_equal("STORED", @rc.set("roma","hname=roma"))
|
243
|
+
assert_equal("hname=roma", @rc.get("roma"))
|
244
|
+
@rc.default_hash_name='test'
|
245
|
+
assert_equal("STORED", @rc.set("roma","hname=test"))
|
246
|
+
assert_equal("hname=test", @rc.get("roma"))
|
247
|
+
con.write("balse\r\n")
|
248
|
+
con.gets
|
249
|
+
con.write "yes\r\n"
|
250
|
+
ret = con.gets
|
251
|
+
con.close
|
252
|
+
|
253
|
+
|
254
|
+
# 再起動
|
255
|
+
ruby_path = File.join(RbConfig::CONFIG["bindir"],
|
256
|
+
RbConfig::CONFIG["ruby_install_name"])
|
257
|
+
path = File.dirname(File.expand_path($PROGRAM_NAME))
|
258
|
+
sh = Shell.new
|
259
|
+
sh.system(ruby_path,"#{path}/../bin/romad","localhost","-p","11211","-d","--verbose")
|
260
|
+
sh.system(ruby_path,"#{path}/../bin/romad","localhost","-p","11212","-d","--verbose")
|
261
|
+
sleep 2
|
262
|
+
Roma::Messaging::ConPool.instance.close_all
|
263
|
+
|
264
|
+
@rc=Roma::Client::RomaClient.new(["localhost_11211","localhost_11212"])
|
265
|
+
@rc.default_hash_name='test'
|
266
|
+
con = Roma::Messaging::ConPool.instance.get_connection("localhost_11211")
|
267
|
+
con.write("hashlist\r\n")
|
268
|
+
ret = con.gets
|
269
|
+
|
270
|
+
#
|
271
|
+
# for file storage
|
272
|
+
#
|
273
|
+
|
274
|
+
# 停止前のデータが残っていることを確認
|
275
|
+
#assert_equal("hname=test", @rc.get("roma"))
|
276
|
+
|
277
|
+
# test ハッシュを削除
|
278
|
+
#con.write("deletehash test\r\n")
|
279
|
+
#ret = con.gets
|
280
|
+
#assert_equal("{\"localhost_11212\"=>\"DELETED\", \"localhost_11211\"=>\"DELETED\"}", ret.chomp )
|
281
|
+
|
282
|
+
# デフォルトハッシュに残ったテストデータを削除
|
283
|
+
#@rc.default_hash_name='roma'
|
284
|
+
#assert_equal('DELETED', @rc.delete("roma"))
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
def test_createhash3
|
289
|
+
con = Roma::Messaging::ConPool.instance.get_connection("localhost_11211")
|
290
|
+
|
291
|
+
# 存在しないハッシュを削除
|
292
|
+
con.write("deletehash test\r\n")
|
293
|
+
ret = con.gets
|
294
|
+
assert_equal("{\"localhost_11212\"=>\"SERVER_ERROR test dose not exists.\", \"localhost_11211\"=>\"SERVER_ERROR test dose not exists.\"}", ret.chomp )
|
295
|
+
|
296
|
+
# デフォルトハッシュを削除
|
297
|
+
con.write("deletehash roma\r\n")
|
298
|
+
ret = con.gets
|
299
|
+
assert_equal("{\"localhost_11212\"=>\"SERVER_ERROR the hash name of 'roma' can't delete.\", \"localhost_11211\"=>\"SERVER_ERROR the hash name of 'roma' can't delete.\"}", ret.chomp )
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
class RClientTestForceForward < RClientTest
|
304
|
+
def setup
|
305
|
+
super
|
306
|
+
@rc.rttable.instance_eval{
|
307
|
+
undef search_node
|
308
|
+
|
309
|
+
def search_node(key); search_node2(key); end
|
310
|
+
|
311
|
+
def search_node2(key)
|
312
|
+
d = Digest::SHA1.hexdigest(key).hex % @hbits
|
313
|
+
@rd.v_idx[d & @search_mask][1]
|
314
|
+
end
|
315
|
+
}
|
316
|
+
end
|
317
|
+
|
318
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
require 'roma/routing/routing_data'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
class RoutingDataTest < Test::Unit::TestCase
|
8
|
+
def setup
|
9
|
+
end
|
10
|
+
|
11
|
+
def teardown
|
12
|
+
end
|
13
|
+
|
14
|
+
def cnt_obj
|
15
|
+
GC.start
|
16
|
+
n=0
|
17
|
+
ObjectSpace.each_object{|o| n+=1 }
|
18
|
+
n
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_object_count
|
22
|
+
c1=cnt_obj
|
23
|
+
rd=Roma::Routing::RoutingData.create(32,9,2,['roma0_11211','roma0_11212'])
|
24
|
+
c2=cnt_obj
|
25
|
+
puts
|
26
|
+
puts "RoutingData.create #{c2-c1} objects"
|
27
|
+
c1=c2
|
28
|
+
rd.save("routing_data.test.route")
|
29
|
+
rd2=Roma::Routing::RoutingData.load("routing_data.test.route")
|
30
|
+
c2=cnt_obj
|
31
|
+
puts "RoutingData.load #{c2-c1} objects"
|
32
|
+
File::unlink("routing_data.test.route")
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_save_load
|
36
|
+
rd=Roma::Routing::RoutingData.create(32,8,1,['roma0_3300'])
|
37
|
+
rd.save("routing_data.test.route")
|
38
|
+
rd2=Roma::Routing::RoutingData.load("routing_data.test.route")
|
39
|
+
assert( YAML.dump(rd) == YAML.dump(rd2) )
|
40
|
+
File::unlink("routing_data.test.route")
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_next_vnode
|
44
|
+
rd=Roma::Routing::RoutingData.create(32,8,1,['roma0_3300'])
|
45
|
+
assert( 0x01000000 == rd.next_vnode(0x00000000) )
|
46
|
+
assert( 0x00000000 == rd.next_vnode(0xff000000) )
|
47
|
+
assert( 0x56000000 == rd.next_vnode(0x55000000) )
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_create_nodes_from_v_idx
|
51
|
+
rd=Roma::Routing::RoutingData.create(32,8,1,['roma0','roma1','roma2'])
|
52
|
+
rd.nodes.clear
|
53
|
+
rd.create_nodes_from_v_idx
|
54
|
+
assert( rd.nodes == ['roma0','roma1','roma2'] )
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_create
|
58
|
+
# ダイジェストの総ビット数 32
|
59
|
+
# バーチャルノードのビット数 8
|
60
|
+
# 冗長度 1
|
61
|
+
# ノードIDの配列 [roma0_3300]
|
62
|
+
rd=Roma::Routing::RoutingData.create(32,8,1,['roma0_3300'])
|
63
|
+
|
64
|
+
assert( rd.v_idx.length==256 )
|
65
|
+
assert( rd.nodes.length==1 )
|
66
|
+
assert( rd.search_mask==0xff000000 )
|
67
|
+
assert( rd.dgst_bits==32 )
|
68
|
+
assert( rd.div_bits==8 )
|
69
|
+
assert( rd.rn==1 )
|
70
|
+
|
71
|
+
# ダイジェストの総ビット数 32
|
72
|
+
# バーチャルノードのビット数 16
|
73
|
+
# 冗長度 2
|
74
|
+
# ノードIDの配列 ['roma0_3300','roma1_3300','roma2_3300']
|
75
|
+
rd=Roma::Routing::RoutingData.create(32,16,2,['roma0_3300','roma1_3300','roma2_3300'])
|
76
|
+
|
77
|
+
assert( rd.v_idx.length==65536 )
|
78
|
+
assert( rd.nodes.length==3 )
|
79
|
+
assert( rd.search_mask==0xffff0000 )
|
80
|
+
assert( rd.dgst_bits==32 )
|
81
|
+
assert( rd.div_bits==16 )
|
82
|
+
assert( rd.rn==2 )
|
83
|
+
|
84
|
+
c0=c1=c2=0
|
85
|
+
rd.v_idx.each_value{|v|
|
86
|
+
case v[0]
|
87
|
+
when 'roma0_3300'
|
88
|
+
c0+=1
|
89
|
+
when 'roma1_3300'
|
90
|
+
c1+=1
|
91
|
+
when 'roma2_3300'
|
92
|
+
c2+=1
|
93
|
+
end
|
94
|
+
}
|
95
|
+
# バラつきは10%より小さいでしょ
|
96
|
+
assert( (c0-c1).abs < rd.v_idx.length/10 )
|
97
|
+
assert( (c1-c2).abs < rd.v_idx.length/10 )
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|