restruct 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,31 +1,35 @@
1
1
  module Restruct
2
2
  class Structure
3
3
 
4
- attr_reader :redis, :id
4
+ attr_reader :connection, :id
5
5
 
6
6
  def initialize(options={})
7
- @redis = options[:redis] || Restruct.redis
7
+ @connection = options[:connection] || Restruct.connection
8
8
  @id = Id.new options[:id] || Restruct.generate_id
9
9
  end
10
10
 
11
11
  def ==(object)
12
12
  object.class == self.class &&
13
13
  object.id == id &&
14
- object.redis == redis
14
+ object.connection == connection
15
15
  end
16
16
  alias_method :eql?, :==
17
17
 
18
18
  def dump
19
- redis.call 'DUMP', id
19
+ connection.call 'DUMP', id
20
20
  end
21
21
 
22
22
  def restore(dump)
23
23
  destroy
24
- redis.call 'RESTORE', id, 0, dump
24
+ connection.lazy 'RESTORE', id, 0, dump
25
25
  end
26
26
 
27
27
  def destroy
28
- redis.call 'DEL', id
28
+ connection.lazy 'DEL', id
29
+ end
30
+
31
+ def exists?
32
+ connection.call('EXISTS', id) == 1
29
33
  end
30
34
 
31
35
  end
@@ -1,3 +1,3 @@
1
1
  module Restruct
2
- VERSION = '0.0.3'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -0,0 +1,21 @@
1
+ local locker_id = ARGV[1]
2
+ local lock_key = ARGV[2]
3
+ local exclusive = ARGV[3]
4
+
5
+ if redis.call('EXISTS', locker_id) == 1 then
6
+ local actual_lock_key = redis.call('HGET', locker_id, 'key')
7
+ local actual_exclusive = redis.call('HGET', locker_id, 'exclusive')
8
+
9
+ local error_message = "Lock " .. lock_key .. " (exclusive=" .. exclusive .. ") fail. Alradey locked by " .. actual_lock_key .. " (exclusive=" .. actual_exclusive .. ")"
10
+
11
+ if exclusive == "true" or
12
+ actual_exclusive == 'true' or
13
+ actual_lock_key ~= lock_key then
14
+
15
+ error(error_message)
16
+ end
17
+ end
18
+
19
+ redis.call('HSET', locker_id, 'key', lock_key)
20
+ redis.call('HSET', locker_id, 'exclusive', exclusive)
21
+ redis.call("HINCRBY", locker_id, 'nested', 1)
@@ -0,0 +1,10 @@
1
+ local locker_id = ARGV[1]
2
+
3
+ if redis.call('EXISTS', locker_id) == 0 then
4
+ error("Unregister Error. Dont exist key " .. locker_id)
5
+ else
6
+ redis.call("HINCRBY", locker_id, 'nested', -1)
7
+ if redis.call('HGET', locker_id, 'nested') == '0' then
8
+ redis.call('DEL', locker_id)
9
+ end
10
+ end
@@ -18,12 +18,13 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.add_dependency 'redic', '~> 1.2.0'
21
+ spec.add_dependency 'redic', '~> 1.5.0'
22
22
  spec.add_dependency 'class_config', '~> 0.0.1'
23
23
 
24
24
  spec.add_development_dependency 'bundler', '~> 1.6'
25
25
  spec.add_development_dependency 'rake'
26
26
  spec.add_development_dependency 'minitest', '~> 4.7'
27
+ spec.add_development_dependency "minitest-great_expectations"
27
28
  spec.add_development_dependency 'turn', '~> 0.9'
28
29
  spec.add_development_dependency 'simplecov'
29
30
  spec.add_development_dependency 'pry-nav'
@@ -7,7 +7,7 @@ require 'minitest_helper'
7
7
  let(:array) { klass.new }
8
8
 
9
9
  def fill(elements)
10
- redis.call 'RPUSH', array.id, *(elements.map { |e| array.send(:serialize, e) })
10
+ connection.call 'RPUSH', array.id, *(elements.map { |e| array.send(:serialize, e) })
11
11
  end
12
12
 
13
13
  describe 'Getters' do
@@ -333,6 +333,42 @@ require 'minitest_helper'
333
333
  other.to_primitive.must_equal array.to_primitive
334
334
  end
335
335
 
336
+ it 'Batch' do
337
+ fill %w(a b c)
338
+
339
+ array.connection.batch do
340
+ array[1] = 'x' #a x c
341
+ array << 'd' #a x c d
342
+ array.shift #x c d
343
+ array.pop(2) #x
344
+ %w(y h z).each {|e| array << e} #x y h z
345
+ array.delete 'h' #x y z
346
+
347
+ array.to_a.must_equal %w(a b c)
348
+ end
349
+
350
+ array.to_a.must_equal %w(x y z)
351
+ end
352
+
353
+ it 'Restore/Destroy in batch' do
354
+ fill %w(a b c)
355
+
356
+ dump = array.dump
357
+ other = klass.new
358
+
359
+ array.connection.batch do
360
+ array.destroy
361
+ other.restore dump
362
+ array.must_be :exists?
363
+ other.wont_be :exists?
364
+ array.to_a.must_equal %w(a b c)
365
+ end
366
+
367
+ array.wont_be :exists?
368
+ other.must_be :exists?
369
+ other.to_a.must_equal %w(a b c)
370
+ end
371
+
336
372
  end
337
373
 
338
374
  end
@@ -0,0 +1,116 @@
1
+ require 'minitest_helper'
2
+
3
+ describe Restruct::Connection do
4
+
5
+ describe 'Success' do
6
+ it 'call' do
7
+ connection.call('HSET', 'test_id', 'key', 'test_key')
8
+ connection.call('HGET', 'test_id', 'key').must_equal 'test_key'
9
+ end
10
+
11
+ it 'script' do
12
+ script = %Q{
13
+ local id = ARGV[1]
14
+ local key = ARGV[2]
15
+ local value = ARGV[3]
16
+ redis.call('HSET', id, key, value)
17
+ return redis.call('HGET', id, key)
18
+ }
19
+ connection.script(script, 0, 'test_id', 'key', 'test_key').must_equal 'test_key'
20
+ end
21
+
22
+ it 'reload script when remove from the redis cache' do
23
+ script = %Q{
24
+ local id = ARGV[1]
25
+ return id
26
+ }
27
+ connection.script(script, 0, 'test').must_equal 'test'
28
+ connection.call 'SCRIPT', 'FLUSH'
29
+ connection.script(script, 0, 'test').must_equal 'test'
30
+ end
31
+
32
+ end
33
+
34
+ describe 'Errors' do
35
+
36
+ it 'ArgumentError' do
37
+ proc { connection.call }.must_raise ArgumentError
38
+ end
39
+
40
+ it 'NoScriptError' do
41
+ proc { connection.call 'EVALSHA', 'invalid sha', 0 }.must_raise Restruct::NoScriptError
42
+ end
43
+
44
+ it 'Invalid Script' do
45
+ proc { connection.script 'xyz', 0 }.must_raise Restruct::ConnectionError
46
+ end
47
+
48
+ it 'RuntimeScriptError' do
49
+ proc { connection.script 'return x', 0 }.must_raise Restruct::ConnectionError
50
+ end
51
+
52
+ end
53
+
54
+ describe 'Batch' do
55
+
56
+ let (:id) {Restruct.generate_id}
57
+
58
+ it 'Execute' do
59
+ connection.lazy('HSET', id, 'key_1', 'x')
60
+
61
+ connection.batch do
62
+ connection.lazy('HSET', id, 'key_1', 'y')
63
+ connection.lazy('HSET', id, 'key_2', 'x')
64
+
65
+ connection.call('HKEYS', id).must_equal ['key_1']
66
+ connection.call('HGET', id, 'key_1').must_equal 'x'
67
+ connection.call('HGET', id, 'key_2').must_be_nil
68
+ end
69
+
70
+ connection.call('HKEYS', id).must_equal ['key_1', 'key_2']
71
+ connection.call('HGET', id, 'key_1').must_equal 'y'
72
+ connection.call('HGET', id, 'key_2').must_equal 'x'
73
+ end
74
+
75
+ it 'Discard' do
76
+ connection.lazy('HSET', id, 'key_1', 'x')
77
+
78
+ proc do
79
+ connection.batch do
80
+ connection.lazy('HSET', id, 'key_1', 'y')
81
+ connection.lazy('HSET', id, 'key_2', 'x')
82
+ raise 'Test error'
83
+ end
84
+ end.must_raise RuntimeError
85
+
86
+ connection.call('HKEYS', id).must_equal ['key_1']
87
+ connection.call('HGET', id, 'key_1').must_equal 'x'
88
+ end
89
+
90
+ it 'Nested' do
91
+ connection.lazy('HSET', id, 'key_1', 'x')
92
+
93
+ connection.batch do
94
+ connection.lazy('HSET', id, 'key_1', 'y')
95
+ connection.lazy('HSET', id, 'key_2', 'x')
96
+
97
+ connection.batch do
98
+ connection.lazy('HSET', id, 'key_1', 'z')
99
+ connection.lazy('HSET', id, 'key_2', 'z')
100
+
101
+ connection.call('HGET', id, 'key_1').must_equal 'x'
102
+ connection.call('HGET', id, 'key_2').must_be_nil
103
+ end
104
+
105
+ connection.call('HGET', id, 'key_1').must_equal 'x'
106
+ connection.call('HGET', id, 'key_2').must_be_nil
107
+ end
108
+
109
+ connection.call('HKEYS', id).must_equal ['key_1', 'key_2']
110
+ connection.call('HGET', id, 'key_1').must_equal 'z'
111
+ connection.call('HGET', id, 'key_2').must_equal 'z'
112
+ end
113
+
114
+ end
115
+
116
+ end
@@ -8,7 +8,7 @@ require 'minitest_helper'
8
8
 
9
9
  def fill(data)
10
10
  data.each { |k,v| data[k] = hash.send(:serialize, v) }
11
- redis.call 'HMSET', hash.id, *data.flatten
11
+ connection.call 'HMSET', hash.id, *data.flatten
12
12
  end
13
13
 
14
14
  describe 'Getters' do
@@ -20,7 +20,7 @@ require 'minitest_helper'
20
20
  hash[:b].must_equal 'y'
21
21
  hash[:c].must_be_nil
22
22
  end
23
-
23
+
24
24
  it 'fetch' do
25
25
  fill a: 'x', b: 'y'
26
26
 
@@ -32,7 +32,7 @@ require 'minitest_helper'
32
32
  error = proc { hash.fetch(:c) }.must_raise KeyError
33
33
  error.message.must_equal 'key not found: c'
34
34
  end
35
-
35
+
36
36
  it 'key' do
37
37
  fill a: 'x', b: 'y', c: 'y'
38
38
 
@@ -237,6 +237,20 @@ require 'minitest_helper'
237
237
  other.to_primitive.must_equal hash.to_primitive
238
238
  end
239
239
 
240
+ it 'Batch' do
241
+ fill a: 'x', b: 'y', c: 'z'
242
+
243
+ hash.connection.batch do
244
+ hash[:d] = 'w'
245
+ hash.delete :a
246
+ hash.merge! b: 'x', e: 'v'
247
+
248
+ hash.to_h.must_equal 'a' => 'x', 'b' => 'y', 'c' => 'z'
249
+ end
250
+
251
+ hash.to_h.must_equal 'b' => 'x', 'c' => 'z', 'd' => 'w', 'e' => 'v'
252
+ end
253
+
240
254
  end
241
255
 
242
256
  end
@@ -0,0 +1,161 @@
1
+ require 'minitest_helper'
2
+
3
+ describe Restruct::Locker do
4
+
5
+ let(:locker) { Restruct::Locker.new }
6
+
7
+ it 'Flexible' do
8
+ locker.wont_be :locked?
9
+
10
+ locker.lock :process_1 do
11
+ locker.must_be :locked?
12
+ locker.locked_by.must_equal 'process_1'
13
+
14
+ locker.lock(:process_1) { }
15
+ locker.to_h.must_equal({"key"=>"process_1", "exclusive"=>"false", "nested"=>"1"})
16
+
17
+ error = proc { locker.lock :process_2 }.must_raise Restruct::LockerError
18
+ error.message.must_equal 'Lock process_2 (exclusive=false) fail. Alradey locked by process_1 (exclusive=false)'
19
+ end
20
+
21
+ locker.wont_be :locked?
22
+ end
23
+
24
+ it 'Strict' do
25
+ locker.wont_be :locked?
26
+
27
+ locker.lock! :process_1 do
28
+ locker.must_be :locked?
29
+ locker.locked_by.must_equal 'process_1'
30
+
31
+ error = proc { locker.lock! :process_1 }.must_raise Restruct::LockerError
32
+ error.message.must_equal 'Lock process_1 (exclusive=true) fail. Alradey locked by process_1 (exclusive=true)'
33
+
34
+ error = proc { locker.lock :process_2 }.must_raise Restruct::LockerError
35
+ error.message.must_equal 'Lock process_2 (exclusive=false) fail. Alradey locked by process_1 (exclusive=true)'
36
+ end
37
+
38
+ locker.wont_be :locked?
39
+ end
40
+
41
+ it 'Force unlock' do
42
+ connection.call('HSET', locker.id, 'key', :process_1)
43
+
44
+ locker.must_be :locked?
45
+ locker.locked_by.must_equal 'process_1'
46
+
47
+ locker.unlock!
48
+
49
+ locker.wont_be :locked?
50
+ end
51
+
52
+ it 'Threads safe' do
53
+ test_id = Restruct.generate_id[:test]
54
+
55
+ threads = 10.times.map do |thread_number|
56
+ Thread.new do
57
+ 10.times do |iteration|
58
+ locker.lock :process_1 do
59
+ connection.call 'RPUSH', test_id, "#{thread_number}-#{iteration}"
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ threads.each(&:join)
66
+
67
+ locker.wont_be :locked?
68
+
69
+ expected_list = []
70
+ 10.times do |i|
71
+ 10.times do |j|
72
+ expected_list << "#{i}-#{j}"
73
+ end
74
+ end
75
+
76
+ list = connection.call('LRANGE', test_id, 0, -1)
77
+ list.sort.must_equal expected_list
78
+ list.wont_equal expected_list
79
+ end
80
+
81
+ if RUBY_ENGINE != 'jruby'
82
+ it 'Multiples process' do
83
+ test_id = Restruct.generate_id[:test]
84
+ locker_id = locker.id
85
+
86
+ pids = 10.times.map do |thread_number|
87
+ Process.fork do
88
+ connection = Restruct::Connection.new
89
+ locker = Restruct::Locker.new id: locker_id, connection: connection
90
+ 10.times do |iteration|
91
+ locker.lock :process_1 do
92
+ connection.call 'RPUSH', test_id, "#{thread_number}-#{iteration}"
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ Process.waitall
99
+
100
+ locker.wont_be :locked?
101
+
102
+ expected_list = []
103
+ 10.times do |i|
104
+ 10.times do |j|
105
+ expected_list << "#{i}-#{j}"
106
+ end
107
+ end
108
+
109
+ list = connection.call('LRANGE', test_id, 0, -1)
110
+ list.sort.must_equal expected_list
111
+ end
112
+ end
113
+
114
+ it 'Return block result' do
115
+ result = locker.lock :process_1 do
116
+ 'OK'
117
+ end
118
+
119
+ result.must_equal 'OK'
120
+ end
121
+
122
+ it 'Unlock when raise error in block' do
123
+ locker.wont_be :locked?
124
+
125
+ proc do
126
+ locker.lock :process_1 do
127
+ raise RuntimeError, 'ERROR'
128
+ end
129
+ end.must_raise RuntimeError, 'ERROR'
130
+
131
+ locker.wont_be :locked?
132
+ end
133
+
134
+ it 'Batch' do
135
+ id = Restruct.generate_id[:test]
136
+ connection.lazy('HSET', id, 'key_1', 'x')
137
+
138
+ connection.batch do
139
+ connection.lazy('HSET', id, 'key_1', 'y')
140
+
141
+ locker.wont_be :locked?
142
+
143
+ locker.lock :process_1 do
144
+ locker.must_be :locked?
145
+ connection.lazy('HSET', id, 'key_2', 'x')
146
+ locker.lock :process_1 do
147
+ connection.lazy('HSET', id, 'key_2', 'z')
148
+ locker.to_h['nested'].must_equal '2'
149
+ end
150
+ locker.must_be :locked?
151
+ locker.to_h['nested'].must_equal '1'
152
+ end
153
+
154
+ locker.wont_be :locked?
155
+ end
156
+ connection.call('HKEYS', id).must_equal ['key_1', 'key_2']
157
+ connection.call('HGET', id, 'key_1').must_equal 'y'
158
+ connection.call('HGET', id, 'key_2').must_equal 'z'
159
+ end
160
+
161
+ end