perobs 4.1.0 → 4.2.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.
@@ -0,0 +1,171 @@
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
+ describe FuzzyStringMatcher do
33
+
34
+ before(:all) do
35
+ @db_name = generate_db_name(__FILE__)
36
+ @store = PEROBS::Store.new(@db_name)
37
+ @fsm = FuzzyStringMatcher.new(@store, 'test')
38
+ @fsm2 = FuzzyStringMatcher.new(@store, 'test', true, 2)
39
+ end
40
+
41
+ after(:all) do
42
+ @store.delete_store
43
+ end
44
+
45
+ it 'should have no matches for empty dict' do
46
+ expect(@fsm.best_matches('foobar')).to eql([])
47
+ expect(stats = @fsm.stats).not_to be_nil
48
+ expect(stats['dictionary_size']).to eql(0)
49
+ expect(stats['max_list_size']).to eql(0)
50
+ expect(stats['avg_list_size']).to eql(0)
51
+ end
52
+
53
+ it 'should learn a word' do
54
+ @fsm.learn('kindergarten')
55
+ expect(stats = @fsm.stats).not_to be_nil
56
+ expect(stats['dictionary_size']).to eql(11)
57
+ expect(stats['max_list_size']).to eql(1)
58
+ expect(stats['avg_list_size']).to eql(1.0)
59
+ end
60
+
61
+ it 'should clear the dictionary' do
62
+ @fsm.clear
63
+ expect(stats = @fsm.stats).not_to be_nil
64
+ expect(stats['dictionary_size']).to eql(0)
65
+ expect(stats['max_list_size']).to eql(0)
66
+ expect(stats['avg_list_size']).to eql(0)
67
+ end
68
+
69
+ it 'should learn some words' do
70
+ %w( one two three four five six seven eight nine ten
71
+ eleven twelve thirteen fourteen fifteen sixteen
72
+ seventeen eighteen nineteen twenty ).each do |w|
73
+ @fsm.learn(w, w)
74
+ end
75
+ expect(stats = @fsm.stats).not_to be_nil
76
+ expect(stats['dictionary_size']).to eql(65)
77
+ expect(stats['max_list_size']).to eql(7)
78
+ expect(stats['avg_list_size']).to be_within(0.001).of(1.415)
79
+ end
80
+
81
+ it 'should find a match' do
82
+ dut = {
83
+ [ 'one' ] => [ [ 'one', 1.0 ] ],
84
+ [ 'three' ] => [ [ 'three', 1.0 ] ],
85
+ [ 'four' ]=> [ [ 'four', 1.0 ], [ 'fourteen', 0.666 ] ],
86
+ [ 'four', 1.0 ]=> [ [ 'four', 1.0 ] ],
87
+ [ 'even' ] => [ [ 'seven', 0.666 ], [ 'eleven', 0.666 ] ],
88
+ [ 'teen' ] => [ ['thirteen', 0.6666666666666666],
89
+ ['fourteen', 0.6666666666666666],
90
+ ['fifteen', 0.6666666666666666],
91
+ ['sixteen', 0.6666666666666666],
92
+ ['seventeen', 0.6666666666666666],
93
+ ['eighteen', 0.6666666666666666],
94
+ ['nineteen', 0.6666666666666666] ],
95
+ [ 'aight' ] => [ [ 'eight', 0.5 ] ],
96
+ [ 'thirdteen' ] => [ [ 'thirteen', 0.5 ] ],
97
+ [ 'shirt teen', 0.3 ] => [ [ 'thirteen', 0.333 ] ]
98
+ }
99
+ check_data_under_test(@fsm, dut)
100
+ end
101
+
102
+ it 'should not find an unknown match' do
103
+ expect(@fsm.best_matches('foobar')).to eql([])
104
+ end
105
+
106
+ it 'should handle a larger text' do
107
+ text =<<-EOT
108
+ MIT License
109
+
110
+ Permission is hereby granted, free of charge, to any person obtaining
111
+ a copy of this software and associated documentation files (the
112
+ "Software"), to deal in the Software without restriction, including
113
+ without limitation the rights to use, copy, modify, merge, publish,
114
+ distribute, sublicense, and/or sell copies of the Software, and to
115
+ permit persons to whom the Software is furnished to do so, subject to
116
+ the following conditions:
117
+
118
+ The above copyright notice and this permission notice shall be
119
+ included in all copies or substantial portions of the Software.
120
+
121
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
122
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
123
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
124
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
125
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
126
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
127
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
128
+ EOT
129
+
130
+ text.split.each do |word|
131
+ @fsm2.learn(word, word)
132
+ end
133
+ stats = @fsm2.stats
134
+ expect(stats['dictionary_size']).to eql(363)
135
+ expect(stats['max_list_size']).to eql(22)
136
+ expect(stats['avg_list_size']).to be_within(0.001).of(2.366)
137
+ end
138
+
139
+ it 'should find case sensitive matches' do
140
+ dut = {
141
+ [ 'SOFTWARE', 0.5, 20 ] => [ [ 'SOFTWARE', 1.0 ], [ 'SOFTWARE.', 0.888 ] ],
142
+ [ 'three', 0.5, 20 ] => [ [ 'the', 0.5 ], [ 'free', 0.5 ] ]
143
+ }
144
+
145
+ check_data_under_test(@fsm2, dut)
146
+ end
147
+
148
+ def check_data_under_test(fsm, dut)
149
+ dut.each do |inputs, reference|
150
+ key = inputs[0]
151
+ results = fsm.best_matches(*inputs)
152
+
153
+ expect(results.length).to eql(reference.length),
154
+ "Wrong number of results for '#{key}': \n#{results}\n#{reference}"
155
+
156
+ reference.each do |key, rating|
157
+ match = results.find { |v| v[0] == key}
158
+ expect(match).not_to be_nil,
159
+ "result is missing key #{key}: #{results}"
160
+ expect(match[0]).to eql(key),
161
+ "Wrong match returned for key #{key}: #{match}"
162
+ expect(match[1]).to be_within(0.001).of(rating),
163
+ "Wrong rating returend for key #{key}: #{match}"
164
+ end
165
+ end
166
+ end
167
+
168
+ end
169
+
170
+ end
171
+
@@ -131,6 +131,10 @@ class LegacyDB
131
131
  true
132
132
  end
133
133
 
134
+ def repair
135
+ @store.check(true)
136
+ end
137
+
134
138
  private
135
139
 
136
140
  def find_separator(str)
@@ -0,0 +1,176 @@
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 'fileutils'
27
+
28
+ require 'spec_helper'
29
+ require 'perobs/SpaceManager'
30
+ require 'perobs/ProgressMeter'
31
+
32
+ describe PEROBS::SpaceManager do
33
+
34
+ before(:all) do
35
+ @db_dir = generate_db_name('SpaceManager')
36
+ FileUtils.mkdir_p(@db_dir)
37
+ @m = PEROBS::SpaceManager.new(@db_dir, PEROBS::ProgressMeter.new, 5)
38
+ PEROBS.log.level = Logger::ERROR
39
+ PEROBS.log.open($stderr)
40
+ end
41
+
42
+ after(:all) do
43
+ FileUtils.rm_rf(@db_dir)
44
+ end
45
+
46
+ it 'should open the space manager' do
47
+ @m.open
48
+ expect(@m.to_a).to eql([])
49
+ end
50
+
51
+ it 'should support adding spaces' do
52
+ @m.add_space(80, 8)
53
+ expect(@m.has_space?(80, 8)).to be true
54
+ expect(@m.to_a).to eql([[80, 8]])
55
+ expect(@m.check).to be true
56
+ @m.add_space(40, 4)
57
+ expect(@m.has_space?(40, 4)).to be true
58
+ expect(@m.to_a).to eql([[40, 4], [80, 8]])
59
+ @m.add_space(20, 2)
60
+ expect(@m.has_space?(20, 2)).to be true
61
+ expect(@m.to_a).to eql([[20, 2], [40, 4], [80, 8]])
62
+ @m.add_space(160, 16)
63
+ expect(@m.has_space?(160, 16)).to be true
64
+ expect(@m.to_a).to eql([[20, 2], [40, 4], [80, 8], [160, 16]])
65
+ @m.add_space(320, 32)
66
+ expect(@m.has_space?(320, 32)).to be true
67
+ expect(@m.to_a).to eql([[20, 2], [40, 4], [80, 8], [160, 16], [320, 32]])
68
+ @m.add_space(100, 10)
69
+ expect(@m.has_space?(100, 10)).to be true
70
+ expect(@m.to_a).to eql([[20, 2], [40, 4], [80, 8], [100, 10], [160, 16], [320, 32]])
71
+ @m.add_space(81, 8)
72
+ expect(@m.has_space?(81, 8)).to be true
73
+ expect(@m.to_a).to eql([[20, 2], [40, 4], [80, 8], [81, 8], [100, 10], [160, 16], [320, 32]])
74
+ expect(@m.check).to be true
75
+ end
76
+
77
+ it 'should keep values over an close/open' do
78
+ @m.add_space(1, 15)
79
+ expect(@m.check).to be true
80
+ @m.close
81
+ @m.open
82
+ expect(@m.check).to be true
83
+ expect(@m.to_a).to eql([[1, 15], [20, 2], [40, 4], [80, 8], [81, 8], [100, 10], [160, 16], [320, 32]])
84
+ end
85
+
86
+ it 'should support clearing all spaces' do
87
+ @m.clear
88
+ expect(@m.to_a).to eql([])
89
+ @m.add_space(1, 1)
90
+ @m.add_space(2, 2)
91
+ @m.clear
92
+ expect(@m.to_a).to eql([])
93
+ end
94
+
95
+ it 'should return exactly matching spaces' do
96
+ @m.clear
97
+ add_sizes([ 10, 5, 15, 10 ])
98
+ expect(@m.to_a).to eql([[0, 10], [1, 5], [2, 15], [3, 10]])
99
+ expect(@m.get_space(10)).to eql([3, 10])
100
+ expect(@m.get_space(10)).to eql([0, 10])
101
+ expect(@m.to_a).to eql([[1, 5], [2, 15]])
102
+ expect(@m.added_spaces).to eql(4)
103
+ expect(@m.recycled_spaces).to eql(2)
104
+ expect(@m.failed_requests).to eql(0)
105
+
106
+ @m.clear
107
+ add_sizes([ 10, 5, 15, 10, 10 ])
108
+ expect(@m.to_a).to eql([[0, 10], [1, 5], [2, 15], [3, 10], [4, 10]])
109
+ expect(@m.get_space(10)).to eql([4, 10])
110
+ expect(@m.get_space(10)).to eql([3, 10])
111
+ expect(@m.get_space(10)).to eql([0, 10])
112
+ expect(@m.to_a).to eql([[1, 5], [2, 15]])
113
+ expect(@m.added_spaces).to eql(5)
114
+ expect(@m.recycled_spaces).to eql(3)
115
+ expect(@m.failed_requests).to eql(0)
116
+ end
117
+
118
+ it "should return nil if no space can be found" do
119
+ expect(@m.get_space(42)).to be nil
120
+ expect(@m.get_space(9)).to be nil
121
+ expect(@m.get_space(11)).to be nil
122
+ expect(@m.recycled_spaces).to eql(3)
123
+ expect(@m.failed_requests).to eql(3)
124
+ end
125
+
126
+ it 'should support a real-world traffic pattern' do
127
+ address = 0
128
+ spaces = []
129
+ @m.clear
130
+ 0.upto(1500) do
131
+ case rand(4)
132
+ when 0
133
+ # Insert new values
134
+ rand(9).times do
135
+ size = 20 + rand(80)
136
+ @m.add_space(address, size)
137
+ spaces << [ address, size ]
138
+ address += size
139
+ end
140
+ when 1
141
+ # Remove some values
142
+ rand(7).times do
143
+ size = rand(110)
144
+ if (space = @m.get_space(size))
145
+ expect(spaces.include?(space)).to be true
146
+ spaces.delete(space)
147
+ end
148
+ end
149
+ when 2
150
+ if rand(10) == 0
151
+ expect(@m.check).to be true
152
+ spaces.each do |address, size|
153
+ expect(@m.has_space?(address, size)).to be true
154
+ end
155
+ @m.to_a.each do |address, size|
156
+ expect(spaces.include?([ address, size ])).to be true
157
+ end
158
+ end
159
+ when 3
160
+ if rand(200) == 0
161
+ expect(@m.check).to be true
162
+ @m.close
163
+ @m.open
164
+ expect(@m.check).to be true
165
+ end
166
+ end
167
+ end
168
+ end
169
+
170
+ def add_sizes(sizes)
171
+ sizes.each_with_index do |size, i|
172
+ @m.add_space(i, size)
173
+ end
174
+ end
175
+
176
+ end
@@ -271,7 +271,6 @@ describe PEROBS::Store do
271
271
  capture_io { expect(store.gc).to eq(0) }
272
272
  p0 = p1 = p2 = nil
273
273
  capture_io { store.exit }
274
- GC.start
275
274
  store = PEROBS::Store.new(@db_file)
276
275
  expect(store['person0']._id).to eq(id0)
277
276
  expect(store['person0'].related._id).to eq(id1)
@@ -279,9 +278,8 @@ describe PEROBS::Store do
279
278
 
280
279
  store['person0'].related = nil
281
280
  capture_io { expect(store.gc).to eq(2) }
282
- GC.start
283
- expect(store.object_by_id(id1)).to be_nil
284
- expect(store.object_by_id(id2)).to be_nil
281
+ stats = store.statistics
282
+ expect(stats.swept_objects).to eql(2)
285
283
  capture_io { store.exit }
286
284
 
287
285
  store = PEROBS::Store.new(@db_file)
@@ -494,7 +492,6 @@ describe PEROBS::Store do
494
492
  # We have the root hash and the Person object.
495
493
  expect(store.statistics[:in_memory_objects]).to eq(2)
496
494
  store.sync
497
- GC.start
498
495
  # Now the Person should be gone from memory.
499
496
  # Ruby 2.3 and later has changed the GC so that this does not work
500
497
  # reliably anymore. The GC seems to operate lazyly.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perobs
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.0
4
+ version: 4.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Schlaeger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-08 00:00:00.000000000 Z
11
+ date: 2020-05-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '10.1'
47
+ version: 12.3.3
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '10.1'
54
+ version: 12.3.3
55
55
  description: Library to provide a persistent object store
56
56
  email:
57
57
  - chris@linux.com
@@ -87,6 +87,7 @@ files:
87
87
  - lib/perobs/FlatFile.rb
88
88
  - lib/perobs/FlatFileBlobHeader.rb
89
89
  - lib/perobs/FlatFileDB.rb
90
+ - lib/perobs/FuzzyStringMatcher.rb
90
91
  - lib/perobs/Handle.rb
91
92
  - lib/perobs/Hash.rb
92
93
  - lib/perobs/IDList.rb
@@ -101,6 +102,7 @@ files:
101
102
  - lib/perobs/PersistentObjectCacheLine.rb
102
103
  - lib/perobs/ProgressMeter.rb
103
104
  - lib/perobs/RobustFile.rb
105
+ - lib/perobs/SpaceManager.rb
104
106
  - lib/perobs/SpaceTree.rb
105
107
  - lib/perobs/SpaceTreeNode.rb
106
108
  - lib/perobs/SpaceTreeNodeLink.rb
@@ -123,6 +125,7 @@ files:
123
125
  - test/EquiBlobsFile_spec.rb
124
126
  - test/FNV_Hash_1a_64_spec.rb
125
127
  - test/FlatFileDB_spec.rb
128
+ - test/FuzzyStringMatcher_spec.rb
126
129
  - test/Hash_spec.rb
127
130
  - test/IDList_spec.rb
128
131
  - test/LegacyDBs/LegacyDB.rb
@@ -134,6 +137,7 @@ files:
134
137
  - test/LegacyDBs/version_3/version
135
138
  - test/LockFile_spec.rb
136
139
  - test/Object_spec.rb
140
+ - test/SpaceManager_spec.rb
137
141
  - test/SpaceTree_spec.rb
138
142
  - test/StackFile_spec.rb
139
143
  - test/Store_spec.rb
@@ -151,7 +155,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
151
155
  requirements:
152
156
  - - ">="
153
157
  - !ruby/object:Gem::Version
154
- version: '2.3'
158
+ version: '2.4'
155
159
  required_rubygems_version: !ruby/object:Gem::Requirement
156
160
  requirements:
157
161
  - - ">="
@@ -159,7 +163,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
159
163
  version: '0'
160
164
  requirements: []
161
165
  rubyforge_project:
162
- rubygems_version: 2.2.5
166
+ rubygems_version: 2.7.6.2
163
167
  signing_key:
164
168
  specification_version: 4
165
169
  summary: Persistent Ruby Object Store
@@ -175,6 +179,7 @@ test_files:
175
179
  - test/EquiBlobsFile_spec.rb
176
180
  - test/FNV_Hash_1a_64_spec.rb
177
181
  - test/FlatFileDB_spec.rb
182
+ - test/FuzzyStringMatcher_spec.rb
178
183
  - test/Hash_spec.rb
179
184
  - test/IDList_spec.rb
180
185
  - test/LegacyDBs/LegacyDB.rb
@@ -186,6 +191,7 @@ test_files:
186
191
  - test/LegacyDBs/version_3/version
187
192
  - test/LockFile_spec.rb
188
193
  - test/Object_spec.rb
194
+ - test/SpaceManager_spec.rb
189
195
  - test/SpaceTree_spec.rb
190
196
  - test/StackFile_spec.rb
191
197
  - test/Store_spec.rb