perobs 4.1.0 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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