perobs 3.0.1 → 4.3.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.
Files changed (75) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +19 -18
  3. data/lib/perobs.rb +2 -0
  4. data/lib/perobs/Array.rb +68 -21
  5. data/lib/perobs/BTree.rb +110 -54
  6. data/lib/perobs/BTreeBlob.rb +14 -13
  7. data/lib/perobs/BTreeDB.rb +11 -10
  8. data/lib/perobs/BTreeNode.rb +551 -197
  9. data/lib/perobs/BTreeNodeCache.rb +10 -8
  10. data/lib/perobs/BTreeNodeLink.rb +11 -1
  11. data/lib/perobs/BigArray.rb +285 -0
  12. data/lib/perobs/BigArrayNode.rb +1002 -0
  13. data/lib/perobs/BigHash.rb +246 -0
  14. data/lib/perobs/BigTree.rb +197 -0
  15. data/lib/perobs/BigTreeNode.rb +873 -0
  16. data/lib/perobs/Cache.rb +47 -22
  17. data/lib/perobs/ClassMap.rb +2 -2
  18. data/lib/perobs/ConsoleProgressMeter.rb +61 -0
  19. data/lib/perobs/DataBase.rb +4 -3
  20. data/lib/perobs/DynamoDB.rb +62 -20
  21. data/lib/perobs/EquiBlobsFile.rb +174 -59
  22. data/lib/perobs/FNV_Hash_1a_64.rb +54 -0
  23. data/lib/perobs/FlatFile.rb +536 -242
  24. data/lib/perobs/FlatFileBlobHeader.rb +120 -84
  25. data/lib/perobs/FlatFileDB.rb +58 -27
  26. data/lib/perobs/FuzzyStringMatcher.rb +175 -0
  27. data/lib/perobs/Hash.rb +129 -35
  28. data/lib/perobs/IDList.rb +144 -0
  29. data/lib/perobs/IDListPage.rb +107 -0
  30. data/lib/perobs/IDListPageFile.rb +180 -0
  31. data/lib/perobs/IDListPageRecord.rb +142 -0
  32. data/lib/perobs/LockFile.rb +3 -0
  33. data/lib/perobs/Object.rb +28 -20
  34. data/lib/perobs/ObjectBase.rb +53 -10
  35. data/lib/perobs/PersistentObjectCache.rb +142 -0
  36. data/lib/perobs/PersistentObjectCacheLine.rb +99 -0
  37. data/lib/perobs/ProgressMeter.rb +97 -0
  38. data/lib/perobs/SpaceManager.rb +273 -0
  39. data/lib/perobs/SpaceTree.rb +63 -47
  40. data/lib/perobs/SpaceTreeNode.rb +134 -115
  41. data/lib/perobs/SpaceTreeNodeLink.rb +1 -1
  42. data/lib/perobs/StackFile.rb +1 -1
  43. data/lib/perobs/Store.rb +180 -70
  44. data/lib/perobs/version.rb +1 -1
  45. data/perobs.gemspec +4 -4
  46. data/test/Array_spec.rb +48 -39
  47. data/test/BTreeDB_spec.rb +2 -2
  48. data/test/BTree_spec.rb +50 -1
  49. data/test/BigArray_spec.rb +261 -0
  50. data/test/BigHash_spec.rb +152 -0
  51. data/test/BigTreeNode_spec.rb +153 -0
  52. data/test/BigTree_spec.rb +259 -0
  53. data/test/EquiBlobsFile_spec.rb +105 -5
  54. data/test/FNV_Hash_1a_64_spec.rb +59 -0
  55. data/test/FlatFileDB_spec.rb +199 -15
  56. data/test/FuzzyStringMatcher_spec.rb +261 -0
  57. data/test/Hash_spec.rb +27 -16
  58. data/test/IDList_spec.rb +77 -0
  59. data/test/LegacyDBs/LegacyDB.rb +155 -0
  60. data/test/LegacyDBs/version_3/class_map.json +1 -0
  61. data/test/LegacyDBs/version_3/config.json +1 -0
  62. data/test/LegacyDBs/version_3/database.blobs +0 -0
  63. data/test/LegacyDBs/version_3/database_spaces.blobs +0 -0
  64. data/test/LegacyDBs/version_3/index.blobs +0 -0
  65. data/test/LegacyDBs/version_3/version +1 -0
  66. data/test/LockFile_spec.rb +9 -6
  67. data/test/Object_spec.rb +5 -5
  68. data/test/SpaceManager_spec.rb +176 -0
  69. data/test/SpaceTree_spec.rb +27 -9
  70. data/test/Store_spec.rb +353 -206
  71. data/test/perobs_spec.rb +7 -3
  72. data/test/spec_helper.rb +9 -4
  73. metadata +59 -16
  74. data/lib/perobs/SpaceTreeNodeCache.rb +0 -76
  75. data/lib/perobs/TreeDB.rb +0 -277
@@ -0,0 +1,261 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright (c) 2020 by Chris Schlaeger <chris@taskjuggler.org>
4
+ #
5
+ # MIT License
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ require 'spec_helper'
27
+ require 'perobs/Store'
28
+ require 'perobs/FuzzyStringMatcher'
29
+
30
+ module PEROBS
31
+
32
+ class WordRef < PEROBS::Object
33
+
34
+ attr_persist :word, :line
35
+
36
+ def initialize(store, word, line)
37
+ super(store)
38
+ self.word = word
39
+ self.line = line
40
+ end
41
+
42
+ end
43
+
44
+ describe FuzzyStringMatcher do
45
+
46
+ before(:all) do
47
+ @db_name = generate_db_name(__FILE__)
48
+ @store = PEROBS::Store.new(@db_name)
49
+ @store['fsm'] = @fsm = @store.new(FuzzyStringMatcher)
50
+ @store['fsm2'] = @fsm2 = @store.new(FuzzyStringMatcher, true, 2)
51
+ end
52
+
53
+ after(:all) do
54
+ @store.delete_store
55
+ end
56
+
57
+ it 'should have no matches for empty dict' do
58
+ expect(@fsm.best_matches('foobar')).to eql([])
59
+ expect(stats = @fsm.stats).not_to be_nil
60
+ expect(stats['dictionary_size']).to eql(0)
61
+ expect(stats['max_list_size']).to eql(0)
62
+ expect(stats['avg_list_size']).to eql(0)
63
+ end
64
+
65
+ it 'should learn a word' do
66
+ @fsm.learn('kindergarten')
67
+ expect(stats = @fsm.stats).not_to be_nil
68
+ expect(stats['dictionary_size']).to eql(11)
69
+ expect(stats['max_list_size']).to eql(1)
70
+ expect(stats['avg_list_size']).to eql(1.0)
71
+ end
72
+
73
+ it 'should clear the dictionary' do
74
+ @fsm.clear
75
+ expect(stats = @fsm.stats).not_to be_nil
76
+ expect(stats['dictionary_size']).to eql(0)
77
+ expect(stats['max_list_size']).to eql(0)
78
+ expect(stats['avg_list_size']).to eql(0)
79
+ end
80
+
81
+ it 'should learn some words' do
82
+ %w( one two three four five six seven eight nine ten
83
+ eleven twelve thirteen fourteen fifteen sixteen
84
+ seventeen eighteen nineteen twenty ).each do |w|
85
+ @fsm.learn(w, w)
86
+ end
87
+ expect(stats = @fsm.stats).not_to be_nil
88
+ expect(stats['dictionary_size']).to eql(65)
89
+ expect(stats['max_list_size']).to eql(7)
90
+ expect(stats['avg_list_size']).to be_within(0.001).of(1.415)
91
+ end
92
+
93
+ it 'should find a match' do
94
+ dut = {
95
+ [ 'one' ] => [ [ 'one', 1.0 ] ],
96
+ [ 'three' ] => [ [ 'three', 1.0 ] ],
97
+ [ 'four' ]=> [ [ 'four', 1.0 ], [ 'fourteen', 0.666 ] ],
98
+ [ 'four', 1.0 ]=> [ [ 'four', 1.0 ] ],
99
+ [ 'even' ] => [ [ 'seven', 0.666 ], [ 'eleven', 0.666 ] ],
100
+ [ 'teen' ] => [ ['thirteen', 0.6666666666666666],
101
+ ['fourteen', 0.6666666666666666],
102
+ ['fifteen', 0.6666666666666666],
103
+ ['sixteen', 0.6666666666666666],
104
+ ['seventeen', 0.6666666666666666],
105
+ ['eighteen', 0.6666666666666666],
106
+ ['nineteen', 0.6666666666666666] ],
107
+ [ 'aight' ] => [ [ 'eight', 0.5 ] ],
108
+ [ 'thirdteen' ] => [ [ 'thirteen', 0.5 ] ],
109
+ [ 'shirt teen', 0.3 ] => [ [ 'thirteen', 0.333 ] ]
110
+ }
111
+ check_data_under_test(@fsm, dut)
112
+ end
113
+
114
+ it 'should not find an unknown match' do
115
+ expect(@fsm.best_matches('foobar')).to eql([])
116
+ end
117
+
118
+ it 'should find a match' do
119
+ dut = {
120
+ [ 'one' ] => [ [ 'one', 1.0 ] ],
121
+ [ 'three' ] => [ [ 'three', 1.0 ] ],
122
+ [ 'four' ]=> [ [ 'four', 1.0 ], [ 'fourteen', 0.666 ] ],
123
+ [ 'four', 1.0 ]=> [ [ 'four', 1.0 ] ],
124
+ [ 'even' ] => [ [ 'seven', 0.666 ], [ 'eleven', 0.666 ] ],
125
+ [ 'teen' ] => [ ['thirteen', 0.6666666666666666],
126
+ ['fourteen', 0.6666666666666666],
127
+ ['fifteen', 0.6666666666666666],
128
+ ['sixteen', 0.6666666666666666],
129
+ ['seventeen', 0.6666666666666666],
130
+ ['eighteen', 0.6666666666666666],
131
+ ['nineteen', 0.6666666666666666] ],
132
+ [ 'aight' ] => [ [ 'eight', 0.5 ] ],
133
+ [ 'thirdteen' ] => [ [ 'thirteen', 0.5 ] ],
134
+ [ 'shirt teen', 0.3 ] => [ [ 'thirteen', 0.333 ] ]
135
+ }
136
+ check_data_under_test(@fsm, dut)
137
+ end
138
+
139
+ it 'should sort best to worst matches' do
140
+ @fsm.clear
141
+ %w( xbar xfoox foor bar foobar barfoo foo rab baar fool xbarx
142
+ foobarx xfoobarx foo_bar ).each do |w|
143
+ @fsm.learn(w, w)
144
+ end
145
+ dut = {
146
+ [ 'foo' ] => [["foo", 1.0], ["foor", 0.5], ["foobar", 0.5],
147
+ ["fool", 0.5], ["foobarx", 0.5], ["foo_bar", 0.5],
148
+ ["barfoo", 0.5]],
149
+ [ 'bar' ] => [["bar", 1.0], ["barfoo", 0.5], ["xbar", 0.5],
150
+ ["foobar", 0.5], ["foo_bar", 0.5]],
151
+ [ 'foobar' ] => [["foobar", 1.0], ["foobarx", 0.8], ["xfoobarx", 0.6]]
152
+ }
153
+ check_data_under_test(@fsm, dut)
154
+ end
155
+
156
+ it 'should handle a larger text' do
157
+ text =<<-EOT
158
+ MIT License
159
+
160
+ Permission is hereby granted, free of charge, to any person obtaining
161
+ a copy of this software and associated documentation files (the
162
+ "Software"), to deal in the Software without restriction, including
163
+ without limitation the rights to use, copy, modify, merge, publish,
164
+ distribute, sublicense, and/or sell copies of the Software, and to
165
+ permit persons to whom the Software is furnished to do so, subject to
166
+ the following conditions:
167
+
168
+ The above copyright notice and this permission notice shall be
169
+ included in all copies or substantial portions of the Software.
170
+
171
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
172
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
173
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
174
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
175
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
176
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
177
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
178
+ EOT
179
+
180
+ text.split.each do |word|
181
+ @fsm2.learn(word, word)
182
+ end
183
+ stats = @fsm2.stats
184
+ expect(stats['dictionary_size']).to eql(352)
185
+ expect(stats['max_list_size']).to eql(22)
186
+ expect(stats['avg_list_size']).to be_within(0.001).of(2.409)
187
+ end
188
+
189
+ it 'should find case sensitive matches' do
190
+ dut = {
191
+ [ 'SOFTWARE', 0.5, 20 ] => [ [ 'SOFTWARE', 1.0 ], [ 'SOFTWARE.', 0.888 ] ],
192
+ [ 'three', 0.5, 20 ] => [ [ 'the', 0.5 ], [ 'free', 0.5 ] ]
193
+ }
194
+
195
+ check_data_under_test(@fsm2, dut)
196
+ end
197
+
198
+ it 'should support references to PEROBS objects' do
199
+ text =<<-EOT
200
+ MIT License
201
+
202
+ Permission is hereby granted, free of charge, to any person obtaining
203
+ a copy of this software and associated documentation files (the
204
+ "Software"), to deal in the Software without restriction, including
205
+ without limitation the rights to use, copy, modify, merge, publish,
206
+ distribute, sublicense, and/or sell copies of the Software, and to
207
+ permit persons to whom the Software is furnished to do so, subject to
208
+ the following conditions:
209
+ EOT
210
+
211
+ line_no = 1
212
+ @store['fsm'] = fsm = @store.new(FuzzyStringMatcher)
213
+ @store['refs'] = refs = @store.new(Array)
214
+ text.each_line do |line|
215
+ line.split.each do |word|
216
+ ref = @store.new(WordRef, word, line_no)
217
+ refs << ref
218
+ fsm.learn(word, ref)
219
+ end
220
+ line_no += 1
221
+ end
222
+
223
+ found_lines = []
224
+ fsm.best_matches('SOFTWARE').each do |match|
225
+ found_lines << match[0].line
226
+ end
227
+ expect(found_lines.sort).to eql([ 4, 5, 5, 7, 8 ])
228
+ end
229
+
230
+ it 'should with small search words' do
231
+ @fsm.clear
232
+ mats = 'Yukihiro Matsumoto'
233
+ @fsm.learn(mats)
234
+ expect(@fsm.best_matches('Yukihiro').first.first).to eql(mats)
235
+ expect(@fsm.best_matches('Mats', 0.3).first.first).to eql(mats)
236
+ end
237
+
238
+ def check_data_under_test(fsm, dut)
239
+ dut.each do |inputs, reference|
240
+ key = inputs[0]
241
+ results = fsm.best_matches(*inputs)
242
+
243
+ expect(results.length).to eql(reference.length),
244
+ "Wrong number of results for '#{key}': \n#{results}\n#{reference}"
245
+
246
+ reference.each do |key, rating|
247
+ match = results.find { |v| v[0] == key}
248
+ expect(match).not_to be_nil,
249
+ "result is missing key #{key}: #{results}"
250
+ expect(match[0]).to eql(key),
251
+ "Wrong match returned for key #{key}: #{match}"
252
+ expect(match[1]).to be_within(0.001).of(rating),
253
+ "Wrong rating returend for key #{key}: #{match}"
254
+ end
255
+ end
256
+ end
257
+
258
+ end
259
+
260
+ end
261
+
data/test/Hash_spec.rb CHANGED
@@ -31,10 +31,9 @@ require 'spec_helper'
31
31
 
32
32
  require 'perobs'
33
33
 
34
-
35
34
  class PO < PEROBS::Object
36
35
 
37
- po_attr :name
36
+ attr_persist :name
38
37
 
39
38
  def initialize(store, name = nil)
40
39
  super(store)
@@ -68,16 +67,28 @@ describe PEROBS::Hash do
68
67
  h['po'] = po = @store.new(PO)
69
68
  po.name = 'foobar'
70
69
  h['b'] = 'B'
70
+ @store['po_key'] = po_key = @store.new(PO)
71
+ po_key.name = 'po key'
72
+ h[po_key] = 'PO Key'
71
73
 
72
74
  expect(h['a']).to eq('A')
73
75
  expect(h['b']).to eq('B')
74
- @store.sync
76
+ expect(h[@store['po_key']]).to eq('PO Key')
77
+ @store.exit
75
78
 
76
79
  @store = PEROBS::Store.new(@db_name)
77
80
  h = @store['h']
78
81
  expect(h['a']).to eq('A')
79
82
  expect(h['b']).to eq('B')
80
83
  expect(h['po'].name).to eq('foobar')
84
+ po_key = @store['po_key']
85
+ expect(po_key.name).to eq('po key')
86
+ expect(h[po_key]).to eq('PO Key')
87
+ end
88
+
89
+ it 'should not allow hash keys that conflict with internal notations' do
90
+ @store['h'] = h = @store.new(PEROBS::Hash)
91
+ expect { h['#<PEROBS::POReference id=1234>'] = 'foo'; @store.sync }.to raise_error(ArgumentError)
81
92
  end
82
93
 
83
94
  it 'should have an each method to iterate' do
@@ -88,6 +99,7 @@ describe PEROBS::Hash do
88
99
  vs = []
89
100
  h.each { |k, v| vs << k + v }
90
101
  expect(vs.sort.join).to eq('aAbBcC')
102
+ @store.exit
91
103
 
92
104
  @store = PEROBS::Store.new(@db_name)
93
105
  @store['h'] = h = @store.new(PEROBS::Hash)
@@ -100,15 +112,15 @@ describe PEROBS::Hash do
100
112
  end
101
113
 
102
114
  # Utility method to create a PEROBS::Hash from a normal Hash.
103
- def cph(hash = nil)
115
+ def cph(hash = nil, id = 'a')
104
116
  a = @store.new(PEROBS::Hash)
105
117
  a.replace(hash) unless hash.nil?
106
- @store['a'] = a
118
+ @store[id] = a
107
119
  end
108
120
 
109
121
  def pcheck
110
122
  yield
111
- @store.sync
123
+ @store.exit
112
124
  @store = PEROBS::Store.new(@db_name)
113
125
  yield
114
126
  end
@@ -142,20 +154,20 @@ describe PEROBS::Hash do
142
154
  end
143
155
 
144
156
  it 'should support merge!' do
145
- h1 = cph({ 1 => 2, 2 => 3, 3 => 4 })
146
- h2 = cph({ 2 => 'two', 4 => 'four' })
157
+ h1 = cph({ '1' => 2, '2' => 3, '3' => 4 }, 'h1')
158
+ h2 = cph({ '2' => 'two', '4' => 'four' }, 'h2')
147
159
 
148
- ha = { 1 => 2, 2 => 'two', 3 => 4, 4 => 'four' }
149
- hb = { 1 => 2, 2 => 3, 3 => 4, 4 => 'four' }
160
+ ha = { '1' => 2, '2' => 'two', '3' => 4, '4' => 'four' }
161
+ hb = { '1' => 2, '2' => 3, '3' => 4, '4' => 'four' }
150
162
 
151
163
  expect(h1.update(h2)).to eq(ha)
152
- pcheck { expect(h1).to eq(ha) }
164
+ pcheck { expect(@store['h1'].to_hash).to eq(ha) }
153
165
 
154
- h1 = cph({ 1 => 2, 2 => 3, 3 => 4 })
155
- h2 = cph({ 2 => 'two', 4 => 'four' })
166
+ h1 = cph({ '1' => 2, '2' => 3, '3' => 4 }, 'h1')
167
+ h2 = cph({ '2' => 'two', '4' => 'four' }, 'h2')
156
168
 
157
169
  expect(h2.update(h1)).to eq(hb)
158
- pcheck { expect(h2).to eq(hb) }
170
+ pcheck { expect(@store['h2'].to_hash).to eq(hb) }
159
171
  end
160
172
 
161
173
  it 'should support inspect' do
@@ -168,9 +180,8 @@ describe PEROBS::Hash do
168
180
  it 'should catch a leaked PEROBS::ObjectBase object' do
169
181
  @store['a'] = a = @store.new(PEROBS::Hash)
170
182
  o = @store.new(PO)
171
- a['a'] = o.get_self
172
183
  PEROBS.log.open(StringIO.new)
173
- expect { @store.sync }.to raise_error(PEROBS::FatalError)
184
+ expect { a['a'] = o.get_self }.to raise_error(PEROBS::FatalError)
174
185
  PEROBS.log.open($stderr)
175
186
  end
176
187
 
@@ -0,0 +1,77 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright (c) 2016, 2017 by Chris Schlaeger <chris@taskjuggler.org>
4
+ #
5
+ # MIT License
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ require 'spec_helper'
27
+ require 'perobs/IDList'
28
+
29
+ module PEROBS
30
+
31
+ describe IDList do
32
+
33
+ before(:all) do
34
+ @db_dir = generate_db_name('IDList')
35
+ FileUtils.mkdir_p(@db_dir)
36
+ @list = PEROBS::IDList.new(@db_dir, 'idlist', 16, 64)
37
+ end
38
+
39
+ after(:all) do
40
+ @list.erase
41
+ FileUtils.rm_rf(@db_dir)
42
+ end
43
+
44
+ it 'should not contain any values' do
45
+ expect(@list.to_a).to eql []
46
+ expect(@list.include?(0)).to be false
47
+ expect(@list.include?(1)).to be false
48
+ expect { @list.check }.to_not raise_error
49
+ end
50
+
51
+ it 'should store a large number of values' do
52
+ vals = []
53
+ 50000.times do
54
+ v = rand(2 ** 64)
55
+ vals << v
56
+
57
+ next if @list.include?(v)
58
+ @list.insert(v)
59
+ #expect(@list.include?(v)).to be true
60
+ 0.upto(rand(10)) do
61
+ v = vals[rand(vals.length)]
62
+ expect(@list.include?(v)).to be true
63
+ end
64
+
65
+ #expect { @list.check }.to_not raise_error if rand(1000) == 0
66
+ end
67
+ expect { @list.check }.to_not raise_error
68
+
69
+ vals.each do |v|
70
+ expect(@list.include?(v)).to be true
71
+ end
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+
@@ -0,0 +1,155 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright (c) 2015 by Chris Schlaeger <chris@taskjuggler.org>
4
+ #
5
+ # MIT License
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+ #
26
+ $:.unshift File.join(File.dirname(__FILE__), '..', '..', 'lib')
27
+
28
+ require 'perobs'
29
+
30
+ # This class creates and manages a simple DB with some toy data to check the
31
+ # conversion routines for legacy DB formats.
32
+ class LegacyDB
33
+
34
+ class Fragment < PEROBS::Object
35
+
36
+ attr_persist :str, :pred, :succ
37
+
38
+ def initialize(p, str, pred = nil)
39
+ super(p)
40
+ self.str = str
41
+ self.pred = pred
42
+ self.succ = nil
43
+ end
44
+
45
+ end
46
+
47
+ N1 = 293
48
+ N2 = 427
49
+
50
+ def initialize(name)
51
+ @name = name
52
+ @store = nil
53
+ end
54
+
55
+ def create
56
+ @store = PEROBS::Store.new(@name)
57
+ @store['fragments'] = @store.new(PEROBS::Array)
58
+ @store['metadata'] = @store.new(PEROBS::Hash)
59
+ @store['by_length'] = @store.new(PEROBS::Hash)
60
+
61
+ # Create a long string of digits.
62
+ number = (N1**N2).to_s
63
+ # Find a suitable digit that we can use a separator to split the long
64
+ # string into smaller strings.
65
+ separator = find_separator(number)
66
+ @store['metadata']['separator'] = separator
67
+ pred = nil
68
+ # Store all the fragments in the @store['fragments'] array.
69
+ number.split(separator).each do |fragment|
70
+ @store['fragments'] << (f = @store.new(Fragment, fragment, pred))
71
+ # Additionally, we create the doubly-linked list of the fragments.
72
+ pred.succ = f if pred
73
+ pred = f
74
+ # And we store the fragments hashed by their length.
75
+ length = fragment.length.to_s
76
+ if @store['by_length'][length].nil?
77
+ @store['by_length'][length] = @store.new(PEROBS::Array)
78
+ end
79
+ @store['by_length'][length] << f
80
+ end
81
+ @store.exit
82
+ end
83
+
84
+ def open
85
+ @store = PEROBS::Store.new(@name)
86
+ end
87
+
88
+ def check
89
+ # Recreate the original number from the @store['fragments'] list.
90
+ number = @store['fragments'].map { |f| f.str }.
91
+ join(@store['metadata']['separator'])
92
+ if number.to_i != N1 ** N2
93
+ raise RuntimeError, "Number mismatch\n#{number}\n#{N1 ** N2}"
94
+ end
95
+
96
+ # Check the total number of digits based on the bash by length.
97
+ fragment_counter = 0
98
+ total_fragment_length = 0
99
+ @store['by_length'].each do |length, fragments|
100
+ fragment_counter += fragments.length
101
+ total_fragment_length += length.to_i * fragments.length
102
+ end
103
+ if number.length != total_fragment_length + fragment_counter - 1
104
+ raise RuntimeError, "Number length mismatch"
105
+ end
106
+
107
+ # Recreate the original number from the linked list forward traversal.
108
+ number = ''
109
+ f = @store['fragments'][0]
110
+ while f
111
+ number += @store['metadata']['separator'] unless number.empty?
112
+ number += f.str
113
+ f = f.succ
114
+ end
115
+ if number.to_i != N1 ** N2
116
+ raise RuntimeError, "Number mismatch\n#{number}\n#{N1 ** N2}"
117
+ end
118
+
119
+ # Recreate the original number from the linked list backwards traversal.
120
+ number = ''
121
+ f = @store['fragments'][-1]
122
+ while f
123
+ number = @store['metadata']['separator'] + number unless number.empty?
124
+ number = f.str + number
125
+ f = f.pred
126
+ end
127
+ if number.to_i != N1 ** N2
128
+ raise RuntimeError, "Number mismatch\n#{number}\n#{N1 ** N2}"
129
+ end
130
+
131
+ true
132
+ end
133
+
134
+ def repair
135
+ @store.check(true)
136
+ end
137
+
138
+ private
139
+
140
+ def find_separator(str)
141
+ 0.upto(9) do |digit|
142
+ c = digit.to_s
143
+ return c if str[0] != c && str[-1] != c
144
+ end
145
+
146
+ raise RuntimeError, "Could not find separator"
147
+ end
148
+
149
+ end
150
+
151
+ #db = LegacyDB.new('test')
152
+ #db.create
153
+ #db.open
154
+ #db.check
155
+