perobs 4.0.0 → 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +27 -16
  3. data/lib/perobs/Array.rb +66 -19
  4. data/lib/perobs/BTree.rb +106 -15
  5. data/lib/perobs/BTreeBlob.rb +4 -3
  6. data/lib/perobs/BTreeDB.rb +5 -4
  7. data/lib/perobs/BTreeNode.rb +482 -156
  8. data/lib/perobs/BTreeNodeLink.rb +10 -0
  9. data/lib/perobs/BigArray.rb +285 -0
  10. data/lib/perobs/BigArrayNode.rb +1002 -0
  11. data/lib/perobs/BigHash.rb +246 -0
  12. data/lib/perobs/BigTree.rb +197 -0
  13. data/lib/perobs/BigTreeNode.rb +873 -0
  14. data/lib/perobs/Cache.rb +48 -10
  15. data/lib/perobs/ConsoleProgressMeter.rb +61 -0
  16. data/lib/perobs/DataBase.rb +4 -3
  17. data/lib/perobs/DynamoDB.rb +57 -15
  18. data/lib/perobs/EquiBlobsFile.rb +155 -50
  19. data/lib/perobs/FNV_Hash_1a_64.rb +54 -0
  20. data/lib/perobs/FlatFile.rb +519 -227
  21. data/lib/perobs/FlatFileBlobHeader.rb +113 -54
  22. data/lib/perobs/FlatFileDB.rb +49 -23
  23. data/lib/perobs/FuzzyStringMatcher.rb +175 -0
  24. data/lib/perobs/Hash.rb +127 -33
  25. data/lib/perobs/IDList.rb +144 -0
  26. data/lib/perobs/IDListPage.rb +107 -0
  27. data/lib/perobs/IDListPageFile.rb +180 -0
  28. data/lib/perobs/IDListPageRecord.rb +142 -0
  29. data/lib/perobs/Object.rb +18 -15
  30. data/lib/perobs/ObjectBase.rb +46 -5
  31. data/lib/perobs/PersistentObjectCache.rb +57 -68
  32. data/lib/perobs/PersistentObjectCacheLine.rb +24 -12
  33. data/lib/perobs/ProgressMeter.rb +97 -0
  34. data/lib/perobs/SpaceManager.rb +273 -0
  35. data/lib/perobs/SpaceTree.rb +21 -12
  36. data/lib/perobs/SpaceTreeNode.rb +53 -61
  37. data/lib/perobs/Store.rb +264 -145
  38. data/lib/perobs/version.rb +1 -1
  39. data/lib/perobs.rb +2 -0
  40. data/perobs.gemspec +4 -4
  41. data/test/Array_spec.rb +15 -6
  42. data/test/BTree_spec.rb +6 -2
  43. data/test/BigArray_spec.rb +261 -0
  44. data/test/BigHash_spec.rb +152 -0
  45. data/test/BigTreeNode_spec.rb +153 -0
  46. data/test/BigTree_spec.rb +259 -0
  47. data/test/EquiBlobsFile_spec.rb +105 -1
  48. data/test/FNV_Hash_1a_64_spec.rb +59 -0
  49. data/test/FlatFileDB_spec.rb +198 -14
  50. data/test/FuzzyStringMatcher_spec.rb +261 -0
  51. data/test/Hash_spec.rb +13 -3
  52. data/test/IDList_spec.rb +77 -0
  53. data/test/LegacyDBs/LegacyDB.rb +155 -0
  54. data/test/LegacyDBs/version_3/class_map.json +1 -0
  55. data/test/LegacyDBs/version_3/config.json +1 -0
  56. data/test/LegacyDBs/version_3/database.blobs +0 -0
  57. data/test/LegacyDBs/version_3/database_spaces.blobs +0 -0
  58. data/test/LegacyDBs/version_3/index.blobs +0 -0
  59. data/test/LegacyDBs/version_3/version +1 -0
  60. data/test/LockFile_spec.rb +9 -6
  61. data/test/SpaceManager_spec.rb +176 -0
  62. data/test/SpaceTree_spec.rb +4 -1
  63. data/test/Store_spec.rb +305 -203
  64. data/test/spec_helper.rb +9 -4
  65. metadata +57 -16
  66. data/lib/perobs/BTreeNodeCache.rb +0 -109
  67. 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,7 +31,6 @@ require 'spec_helper'
31
31
 
32
32
  require 'perobs'
33
33
 
34
-
35
34
  class PO < PEROBS::Object
36
35
 
37
36
  attr_persist :name
@@ -68,9 +67,13 @@ 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')
76
+ expect(h[@store['po_key']]).to eq('PO Key')
74
77
  @store.exit
75
78
 
76
79
  @store = PEROBS::Store.new(@db_name)
@@ -78,6 +81,14 @@ describe PEROBS::Hash do
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
@@ -169,9 +180,8 @@ describe PEROBS::Hash do
169
180
  it 'should catch a leaked PEROBS::ObjectBase object' do
170
181
  @store['a'] = a = @store.new(PEROBS::Hash)
171
182
  o = @store.new(PO)
172
- a['a'] = o.get_self
173
183
  PEROBS.log.open(StringIO.new)
174
- expect { @store.sync }.to raise_error(PEROBS::FatalError)
184
+ expect { a['a'] = o.get_self }.to raise_error(PEROBS::FatalError)
175
185
  PEROBS.log.open($stderr)
176
186
  end
177
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
+
@@ -0,0 +1 @@
1
+ {"PEROBS::Hash":0,"PEROBS::Array":1,"LegacyDB::Fragment":2}
@@ -0,0 +1 @@
1
+ {"serializer":{"json_class":"Symbol","s":"json"}}
@@ -0,0 +1 @@
1
+ 3
@@ -30,7 +30,11 @@ require 'perobs/LockFile'
30
30
  describe PEROBS::LockFile do
31
31
 
32
32
  before(:each) do
33
- @dir = Dir.mktmpdir('LockFile')
33
+ PEROBS.log.open($stderr)
34
+ PEROBS.log.level = Logger::INFO
35
+ @dir = File.join(Dir.tmpdir,
36
+ "#{File.basename('LockFile_spec')}.#{rand(2**32)}")
37
+ FileUtils.mkdir_p(@dir)
34
38
  @file = File.join(@dir, 'LockFile.lock')
35
39
  end
36
40
 
@@ -42,7 +46,6 @@ describe PEROBS::LockFile do
42
46
  capture_io do
43
47
  expect(PEROBS::LockFile.new('/foo/bar/foobar').lock).to be false
44
48
  end
45
- PEROBS.log.open($stderr)
46
49
  end
47
50
 
48
51
  it 'should support taking and releasing the lock' do
@@ -59,7 +62,7 @@ describe PEROBS::LockFile do
59
62
  expect(lock.is_locked?).to be true
60
63
  lock.forced_unlock
61
64
  expect(lock.is_locked?).to be false
62
- out = capture_io{ expect(lock.unlock).to be false }
65
+ out = capture_io{ expect(lock.unlock).to be false }.log
63
66
  expect(out).to include('There is no current lock to release')
64
67
  end
65
68
 
@@ -67,7 +70,7 @@ describe PEROBS::LockFile do
67
70
  lock1 = PEROBS::LockFile.new(@file)
68
71
  expect(lock1.lock).to be true
69
72
  lock2 = PEROBS::LockFile.new(@file)
70
- out = capture_io { expect(lock2.lock).to be false }
73
+ out = capture_io { expect(lock2.lock).to be false }.log
71
74
  expect(out).to include('due to timeout')
72
75
  expect(lock1.unlock).to be true
73
76
  expect(lock2.lock).to be true
@@ -105,7 +108,7 @@ describe PEROBS::LockFile do
105
108
  end
106
109
  lock2 = PEROBS::LockFile.new(@file,
107
110
  { :max_retries => 2, :pause_secs => 0.5 })
108
- out = capture_io { expect(lock2.lock).to be false }
111
+ out = capture_io { expect(lock2.lock).to be false }.log
109
112
  expect(out).to include('due to timeout')
110
113
  Process.wait(pid)
111
114
  end
@@ -123,7 +126,7 @@ describe PEROBS::LockFile do
123
126
  end
124
127
 
125
128
  lock2 = PEROBS::LockFile.new(@file, { :timeout_secs => 1 })
126
- out = capture_io { expect(lock2.lock).to be true }
129
+ out = capture_io { expect(lock2.lock).to be true }.log
127
130
  expect(out).to include('Old lock file found for PID')
128
131
  expect(lock2.unlock).to be true
129
132
  Process.wait(pid)