dr-rubbis 0.0.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,88 @@
1
+ require 'redis'
2
+
3
+ require 'rubbis/server'
4
+
5
+ module AcceptanceHelpers
6
+ def client
7
+ Redis.new(host: 'localhost', port: TEST_PORT)
8
+ end
9
+
10
+ def with_server(opts = {})
11
+ opts[:server_file] = opts.fetch(:server_file, true)
12
+ server = nil
13
+ server_thread = Thread.new do
14
+ begin
15
+ server = Rubbis::Server.new(
16
+ port: TEST_PORT,
17
+ server_file: opts[:server_file] ? test_db : nil,
18
+ aof_file: opts[:aof_file] ? aof_test_db : nil
19
+ )
20
+ server.listen
21
+ rescue
22
+ puts "[ERRS]"
23
+ puts $!
24
+ puts $!.backtrace
25
+ end
26
+ end
27
+
28
+ wait_for_open_port TEST_PORT
29
+
30
+ yield
31
+ rescue TimeoutError, Redis::CannotConnectError
32
+ server_thread.value unless server_thread.alive?
33
+
34
+ raise
35
+ ensure
36
+ server.shutdown if server
37
+ end
38
+
39
+ def wait_for_open_port(port)
40
+ time = Time.now
41
+ while !check_port(port) && time > Time.now - 3
42
+ sleep 0.01
43
+ end
44
+ raise TimeoutError unless check_port(port)
45
+ end
46
+
47
+ def check_port(port)
48
+ `nc -z localhost #{port}`
49
+ res = $?.success?
50
+ puts $?.inspect unless res
51
+ res
52
+ end
53
+
54
+
55
+ end
56
+
57
+ class FakeClock
58
+ def initialize
59
+ @t = 0
60
+ end
61
+
62
+ def now
63
+ @t
64
+ end
65
+
66
+ def sleep(seconds)
67
+ @t += seconds
68
+ end
69
+ end
70
+
71
+ TEST_PORT = 6380
72
+
73
+ RSpec.configure do |c|
74
+ def test_db
75
+ Dir.tmpdir + '/rubbis_test.dump'
76
+ end
77
+
78
+ def aof_test_db
79
+ Dir.tmpdir + '/rubbis_test.aof'
80
+ end
81
+
82
+ c.before(:each) do
83
+ FileUtils.rm_f(test_db)
84
+ end
85
+
86
+
87
+ c.include AcceptanceHelpers, acceptance: true
88
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+ require 'rubbis/protocol'
3
+
4
+ describe Rubbis::Protocol, :unit do
5
+ describe '.marshal' do
6
+ def self.it_marshals(ruby, wire)
7
+ specify ruby.inspect do
8
+ expect(described_class.marshal ruby).to eq(wire)
9
+ end
10
+ end
11
+
12
+ it_marshals :ok , "+OK\r\n"
13
+ it_marshals nil, "$-1\r\n"
14
+ it_marshals 5, ":5\r\n"
15
+ it_marshals "hello", "$5\r\nhello\r\n"
16
+ it_marshals ["a","bc"], "*2\r\n$1\r\na\r\n$2\r\nbc\r\n"
17
+ it_marshals Rubbis::Error.incorrect_args('cmd'),\
18
+ "-ERR wrong number of arguments for 'cmd' command\r\n"
19
+ end
20
+ end
@@ -0,0 +1,238 @@
1
+ require 'spec_helper'
2
+ require 'rubbis/state'
3
+
4
+ describe Rubbis::State, :unit do
5
+ let(:clock) {FakeClock.new}
6
+ let(:state) {described_class.new(clock)}
7
+
8
+ shared_examples "passive expiry" do |set_cmd, get_cmd|
9
+ it 'expires a key passively' do
10
+ key = "abc"
11
+
12
+ set_cmd.call(state, key)
13
+ state.expire(key, "1")
14
+
15
+ clock.sleep 0.9
16
+ get_cmd.call(state,key)
17
+ expect(state.exists(key)).to eq(1)
18
+
19
+ clock.sleep 0.1
20
+ get_cmd.call(state,key)
21
+ expect(state.exists(key)).to eq(0)
22
+ end
23
+ end
24
+
25
+ describe '#set' do
26
+ it 'sets a value' do
27
+ expect(state.set("abc", "123")).to eq(:ok)
28
+ expect(state.get("abc")).to eq("123")
29
+ end
30
+ it 'does not overwrite n existing value with NX' do
31
+ expect(state.set("abc", "123", "NX")).to eq(:ok)
32
+ expect(state.set("abc", "456", "NX")).to eq(nil)
33
+ expect(state.get("abc")).to eq("123")
34
+ end
35
+
36
+ it 'doen not set a new value with XX' do
37
+ expect(state.set("abc", "123", "XX")).to eq(nil)
38
+ state.set("abc", "123")
39
+ expect(state.set("abc", "456", "XX")).to eq(:ok)
40
+ expect(state.get("abc")).to eq("456")
41
+ end
42
+
43
+ it 'raises error when wrong number of arguments' do
44
+ expect(state.set("abc")).to \
45
+ eq(Rubbis::Error.incorrect_args('set'))
46
+ end
47
+ end
48
+
49
+ describe '#hset' do
50
+ it 'sets a value' do
51
+ expect(state.hset("myhash", "abc", "123")).to eq(:ok)
52
+ expect(state.hset("otherhash", "abc", "456")).to eq(:ok)
53
+ expect(state.hget("myhash", "abc")).to eq("123")
54
+ end
55
+ end
56
+
57
+ describe "#hget" do
58
+ include_examples 'passive expiry',
59
+ -> s,k { s.hset(k, "abc", "123")},
60
+ -> s,k { s.hincrby(k,"abc", "1")}
61
+ end
62
+
63
+ describe "#hmget" do
64
+ it 'gets multiple values' do
65
+ expect(state.hset("myhash", "abc", "123")).to eq(:ok)
66
+ expect(state.hset("myhash", "def", "456")).to eq(:ok)
67
+ expect(state.hmget("myhash", "abc", "def")).to eq(["123", "456"])
68
+ end
69
+
70
+ it 'returns error when not hash value' do
71
+ state.set("myhash", "bogus")
72
+ expect(state.hmget("myhash", "key")).to \
73
+ eq(Rubbis::Error.type_error)
74
+ end
75
+
76
+ it 'returns nils when empty' do
77
+ expect(state.hmget("myhash", "key")).to eq([nil])
78
+ end
79
+
80
+ include_examples 'passive expiry',
81
+ -> s,k { s.hset(k,"abc", "123")},
82
+ -> s,k { s.hmget(k, "abc")}
83
+ end
84
+
85
+ describe "#hincrby" do
86
+ it 'increments a counter stored in a hash' do
87
+ state.hset("myhash", "abc", "123")
88
+ expect(state.hincrby("myhash", "abc", "2")).to eq(125)
89
+ end
90
+
91
+ include_examples 'passive expiry',
92
+ -> s,k { s.hset(k, "abc", "123")},
93
+ -> s,k { s.hincrby(k,"abc", "1")}
94
+ end
95
+
96
+
97
+ describe '#get' do
98
+ include_examples 'passive expiry',
99
+ -> s,k { s.set(k,"123") },
100
+ -> s,k { s.get(k) }
101
+ end
102
+
103
+ describe "#exists" do
104
+ it 'returns the number of keys existing' do
105
+ expect(state.exists("abc")).to eq(0)
106
+ state.set("abc", "123")
107
+ expect(state.exists("abc")).to eq(1)
108
+ end
109
+ end
110
+
111
+ describe '#keys' do
112
+ it 'returns all keys in the system for *' do
113
+ state.set("abc", "123")
114
+ state.set("def", "456")
115
+ expect(state.keys('*')).to eq(%w(abc def))
116
+ end
117
+ end
118
+
119
+ describe 'sorted sets' do
120
+ it 'fetches keys by rank' do
121
+ set_name = 'leaderboard'
122
+ state.zadd(set_name, "1000", "a")
123
+ state.zadd(set_name, "3000", "b")
124
+ state.zadd(set_name, "2000", "c")
125
+ expect(state.zrange(set_name, "0", "1")).to \
126
+ eq(%w(a c))
127
+ end
128
+
129
+ it 'fetches rank by member' do
130
+ set_name = 'leaderboard'
131
+ state.zadd(set_name, "1000", "a")
132
+ state.zadd(set_name, "3000", "b")
133
+ state.zadd(set_name, "2000", "c")
134
+ expect(state.zrank(set_name, "c")).to \
135
+ eq(1)
136
+ end
137
+
138
+ it 'fetches rank by member' do
139
+ set_name = 'leaderboard'
140
+ state.zadd(set_name, "1000", "a")
141
+ state.zadd(set_name, "3000", "b")
142
+ state.zadd(set_name, "2000", "c")
143
+ expect(state.zscore(set_name, "c")).to \
144
+ eq(2000)
145
+ end
146
+
147
+ it 'breaks ties based on score' do
148
+ set_name = 'leaderboard'
149
+ state.zadd(set_name, "1000", "a")
150
+ state.zadd(set_name, "1000", "b")
151
+ state.zadd(set_name, "1000", "c")
152
+ expect(state.zrange(set_name, "0", "1")).to \
153
+ eq(%w(a b))
154
+ end
155
+ end
156
+
157
+ describe 'lists' do
158
+ it 'supports basic operations' do
159
+ state.lpush("q", "a")
160
+ state.lpush("q", "b")
161
+ state.lpush("q", "c")
162
+
163
+ expect(state.llen("q")).to eq(3)
164
+ expect(state.lrange("q", "0","1")).to eq(%w(c b))
165
+
166
+ expect(state.rpop("q")).to eq("a")
167
+ expect(state.llen("q")).to eq(2)
168
+ end
169
+
170
+ it 'support rpoplpush' do
171
+ state.lpush("q", "a")
172
+ state.lpush("q", "b")
173
+ state.lpush("q", "c")
174
+
175
+ expect(state.rpoplpush("q", "p")).to eq("a")
176
+ expect(state.lrange("q", "0", "-1")).to eq(%w(c b))
177
+ expect(state.lrange("p", "0", "-1")).to eq(%w(a))
178
+ end
179
+
180
+ it 'support cyclic rpoplpush' do
181
+ state.lpush("q", "a")
182
+ state.lpush("q", "b")
183
+ state.lpush("q", "c")
184
+
185
+ expect(state.rpoplpush("q", "q")).to eq("a")
186
+ expect(state.lrange("q", "0", "-1")).to eq(%w(a c b))
187
+ end
188
+ end
189
+
190
+ describe '#pexpireat' do
191
+ it 'sets absolute expiry' do
192
+ state.set("abc", "123")
193
+ state.pexpireat("abc", "1000")
194
+ clock.sleep(0.9)
195
+ expect(state.get("abc")).to eq("123")
196
+ clock.sleep(0.1)
197
+ expect(state.get("abc")).to eq(nil)
198
+ end
199
+ end
200
+
201
+ describe '#minimal_log' do
202
+ it 'handles string values' do
203
+ state.set('abc', '123')
204
+ state.set('abc', '123')
205
+ state.set('abc', '456')
206
+ expect(state.minimal_log).to eq([%w(set abc 456)])
207
+ end
208
+
209
+ it 'handles lists' do
210
+ state.lpush('abc', '123')
211
+ state.lpush('abc', '456')
212
+ state.lpush('abc', '789')
213
+ state.rpop('abc')
214
+ expect(state.minimal_log).to eq([
215
+ %w(lpush abc 456),
216
+ %w(lpush abc 789),
217
+ ])
218
+ end
219
+
220
+ it 'handles expiry' do
221
+ state.set("abc", "123")
222
+ state.expire("abc", "1")
223
+
224
+ expect(state.minimal_log).to eq([
225
+ %w(set abc 123),
226
+ %w(pexpireat abc 1000),
227
+ ])
228
+ end
229
+ end
230
+ end
231
+
232
+
233
+
234
+
235
+
236
+
237
+
238
+
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dr-rubbis
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Dror Nir
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-06 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A ruby redis server based on online tutorial
14
+ email: dror.nir0@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - Gemfile
20
+ - Gemfile.lock
21
+ - README.md
22
+ - bin/rspec
23
+ - bin/server
24
+ - lib/dr_rubbis.rb
25
+ - lib/rubbis/handler.rb
26
+ - lib/rubbis/protocol.rb
27
+ - lib/rubbis/server.rb
28
+ - lib/rubbis/state.rb
29
+ - lib/rubbis/transaction.rb
30
+ - lib/rubbis/zset.rb
31
+ - spec/acceptance/expiry_spec.rb
32
+ - spec/acceptance/hash_spec.rb
33
+ - spec/acceptance/list_spec.rb
34
+ - spec/acceptance/persistence_spec.rb
35
+ - spec/acceptance/pubsub_spec.rb
36
+ - spec/acceptance/skeleton_spec.rb
37
+ - spec/acceptance/transactions_spec.rb
38
+ - spec/spec_helper.rb
39
+ - spec/unit/protocol_spec.rb
40
+ - spec/unit/state_spec.rb
41
+ homepage: ''
42
+ licenses:
43
+ - MIT
44
+ metadata: {}
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 2.4.6
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: Mock redis server
65
+ test_files: []