perobs 4.0.0 → 4.4.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 (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)