mudis 0.9.1 → 0.9.4

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.
@@ -99,32 +99,93 @@ RSpec.describe MudisClient do # rubocop:disable Metrics/BlockLength
99
99
  end
100
100
  end
101
101
 
102
- describe "#reset_metrics!" do
103
- it "sends a reset_metrics command" do
104
- payload = { cmd: "reset_metrics" }
105
- response = { ok: true, value: nil }.to_json
102
+ describe "#inspect" do
103
+ it "sends an inspect command and returns metadata" do
104
+ payload = { cmd: "inspect", key: "test_key", namespace: nil }
105
+ response = { ok: true, value: { key: "test_key", size_bytes: 10 } }.to_json
106
106
 
107
107
  expect(mock_socket).to receive(:puts).with(payload.to_json)
108
108
  expect(mock_socket).to receive(:gets).and_return(response)
109
109
 
110
- expect(client.reset_metrics!).to be_nil
110
+ expect(client.inspect("test_key")).to eq({ key: "test_key", size_bytes: 10 })
111
111
  end
112
112
  end
113
113
 
114
- describe "#reset!" do
115
- it "sends a reset command" do
116
- payload = { cmd: "reset" }
114
+ describe "#keys" do
115
+ it "sends a keys command and returns keys" do
116
+ payload = { cmd: "keys", namespace: "ns" }
117
+ response = { ok: true, value: ["a", "b"] }.to_json
118
+
119
+ expect(mock_socket).to receive(:puts).with(payload.to_json)
120
+ expect(mock_socket).to receive(:gets).and_return(response)
121
+
122
+ expect(client.keys(namespace: "ns")).to eq(["a", "b"])
123
+ end
124
+ end
125
+
126
+ describe "#clear_namespace" do
127
+ it "sends a clear_namespace command" do
128
+ payload = { cmd: "clear_namespace", namespace: "ns" }
117
129
  response = { ok: true, value: nil }.to_json
118
130
 
119
131
  expect(mock_socket).to receive(:puts).with(payload.to_json)
120
132
  expect(mock_socket).to receive(:gets).and_return(response)
121
133
 
122
- expect(client.reset!).to be_nil
134
+ expect(client.clear_namespace(namespace: "ns")).to be_nil
135
+ end
136
+ end
137
+
138
+ describe "#least_touched" do
139
+ it "sends a least_touched command" do
140
+ payload = { cmd: "least_touched", limit: 5 }
141
+ response = { ok: true, value: [["a", 0], ["b", 1]] }.to_json
142
+
143
+ expect(mock_socket).to receive(:puts).with(payload.to_json)
144
+ expect(mock_socket).to receive(:gets).and_return(response)
145
+
146
+ expect(client.least_touched(5)).to eq([["a", 0], ["b", 1]])
147
+ end
148
+ end
149
+
150
+ describe "#all_keys" do
151
+ it "sends an all_keys command" do
152
+ payload = { cmd: "all_keys" }
153
+ response = { ok: true, value: ["k1"] }.to_json
154
+
155
+ expect(mock_socket).to receive(:puts).with(payload.to_json)
156
+ expect(mock_socket).to receive(:gets).and_return(response)
157
+
158
+ expect(client.all_keys).to eq(["k1"])
159
+ end
160
+ end
161
+
162
+ describe "#current_memory_bytes" do
163
+ it "sends a current_memory_bytes command" do
164
+ payload = { cmd: "current_memory_bytes" }
165
+ response = { ok: true, value: 123 }.to_json
166
+
167
+ expect(mock_socket).to receive(:puts).with(payload.to_json)
168
+ expect(mock_socket).to receive(:gets).and_return(response)
169
+
170
+ expect(client.current_memory_bytes).to eq(123)
171
+ end
172
+ end
173
+
174
+ describe "#max_memory_bytes" do
175
+ it "sends a max_memory_bytes command" do
176
+ payload = { cmd: "max_memory_bytes" }
177
+ response = { ok: true, value: 456 }.to_json
178
+
179
+ expect(mock_socket).to receive(:puts).with(payload.to_json)
180
+ expect(mock_socket).to receive(:gets).and_return(response)
181
+
182
+ expect(client.max_memory_bytes).to eq(456)
123
183
  end
124
184
  end
125
185
 
126
186
  describe "error handling" do
127
187
  it "warns when the socket is missing" do
188
+ allow(MudisIPCConfig).to receive(:retries).and_return(1)
128
189
  if MudisIPCConfig.use_tcp?
129
190
  allow(TCPSocket).to receive(:new).and_raise(Errno::ECONNREFUSED)
130
191
  else
@@ -143,5 +204,18 @@ RSpec.describe MudisClient do # rubocop:disable Metrics/BlockLength
143
204
 
144
205
  expect { client.read("test_key") }.to raise_error("Something went wrong")
145
206
  end
207
+
208
+ it "retries on timeout and then warns" do
209
+ allow(MudisIPCConfig).to receive(:retries).and_return(1)
210
+ allow(MudisIPCConfig).to receive(:timeout).and_return(0.01)
211
+
212
+ if MudisIPCConfig.use_tcp?
213
+ allow(TCPSocket).to receive(:new).and_raise(Timeout::Error)
214
+ else
215
+ allow(UNIXSocket).to receive(:open).and_raise(Timeout::Error)
216
+ end
217
+
218
+ expect { client.read("test_key") }.to output(/Cannot connect/).to_stderr
219
+ end
146
220
  end
147
221
  end
@@ -13,8 +13,6 @@ RSpec.describe MudisServer do # rubocop:disable Metrics/BlockLength
13
13
  next
14
14
  end
15
15
 
16
- let(:socket_path) { MudisIPCConfig::SOCKET_PATH }
17
-
18
16
  before(:all) do
19
17
  # Start the server once for all tests
20
18
  Thread.new { MudisServer.start! }
@@ -27,12 +25,18 @@ RSpec.describe MudisServer do # rubocop:disable Metrics/BlockLength
27
25
  allow(Mudis).to receive(:delete)
28
26
  allow(Mudis).to receive(:exists?).and_return(true)
29
27
  allow(Mudis).to receive(:fetch).and_return("mock_fetched_value")
28
+ allow(Mudis).to receive(:inspect).and_return({ key: "test_key", size_bytes: 10 })
29
+ allow(Mudis).to receive(:keys).and_return(["a", "b"])
30
+ allow(Mudis).to receive(:clear_namespace)
31
+ allow(Mudis).to receive(:least_touched).and_return([["a", 0]])
32
+ allow(Mudis).to receive(:all_keys).and_return(["k1"])
33
+ allow(Mudis).to receive(:current_memory_bytes).and_return(123)
34
+ allow(Mudis).to receive(:max_memory_bytes).and_return(456)
30
35
  allow(Mudis).to receive(:metrics).and_return({ reads: 1, writes: 1 })
31
- allow(Mudis).to receive(:reset_metrics!)
32
- allow(Mudis).to receive(:reset!)
33
36
  end
34
37
 
35
- after do
38
+ after(:all) do
39
+ socket_path = MudisIPCConfig::SOCKET_PATH
36
40
  File.unlink(socket_path) if File.exist?(socket_path) && !MudisIPCConfig.use_tcp?
37
41
  end
38
42
 
@@ -43,7 +47,7 @@ RSpec.describe MudisServer do # rubocop:disable Metrics/BlockLength
43
47
  JSON.parse(sock.gets, symbolize_names: true)
44
48
  end
45
49
  else
46
- UNIXSocket.open(socket_path) do |sock|
50
+ UNIXSocket.open(MudisIPCConfig::SOCKET_PATH) do |sock|
47
51
  sock.puts(JSON.dump(request))
48
52
  JSON.parse(sock.gets, symbolize_names: true)
49
53
  end
@@ -81,22 +85,52 @@ RSpec.describe MudisServer do # rubocop:disable Metrics/BlockLength
81
85
  expect(Mudis).to have_received(:fetch).with("test_key", expires_in: 60, namespace: "test_ns")
82
86
  end
83
87
 
84
- it "handles the 'metrics' command" do
85
- response = send_request({ cmd: "metrics" })
86
- expect(response).to eq({ ok: true, value: { reads: 1, writes: 1 } })
87
- expect(Mudis).to have_received(:metrics)
88
+ it "handles the 'inspect' command" do
89
+ response = send_request({ cmd: "inspect", key: "test_key", namespace: "test_ns" })
90
+ expect(response).to eq({ ok: true, value: { key: "test_key", size_bytes: 10 } })
91
+ expect(Mudis).to have_received(:inspect).with("test_key", namespace: "test_ns")
88
92
  end
89
93
 
90
- it "handles the 'reset_metrics' command" do
91
- response = send_request({ cmd: "reset_metrics" })
92
- expect(response).to eq({ ok: true, value: nil })
93
- expect(Mudis).to have_received(:reset_metrics!)
94
+ it "handles the 'keys' command" do
95
+ response = send_request({ cmd: "keys", namespace: "test_ns" })
96
+ expect(response).to eq({ ok: true, value: ["a", "b"] })
97
+ expect(Mudis).to have_received(:keys).with(namespace: "test_ns")
94
98
  end
95
99
 
96
- it "handles the 'reset' command" do
97
- response = send_request({ cmd: "reset" })
100
+ it "handles the 'clear_namespace' command" do
101
+ response = send_request({ cmd: "clear_namespace", namespace: "test_ns" })
98
102
  expect(response).to eq({ ok: true, value: nil })
99
- expect(Mudis).to have_received(:reset!)
103
+ expect(Mudis).to have_received(:clear_namespace).with(namespace: "test_ns")
104
+ end
105
+
106
+ it "handles the 'least_touched' command" do
107
+ response = send_request({ cmd: "least_touched", limit: 5 })
108
+ expect(response).to eq({ ok: true, value: [["a", 0]] })
109
+ expect(Mudis).to have_received(:least_touched).with(5)
110
+ end
111
+
112
+ it "handles the 'all_keys' command" do
113
+ response = send_request({ cmd: "all_keys" })
114
+ expect(response).to eq({ ok: true, value: ["k1"] })
115
+ expect(Mudis).to have_received(:all_keys)
116
+ end
117
+
118
+ it "handles the 'current_memory_bytes' command" do
119
+ response = send_request({ cmd: "current_memory_bytes" })
120
+ expect(response).to eq({ ok: true, value: 123 })
121
+ expect(Mudis).to have_received(:current_memory_bytes)
122
+ end
123
+
124
+ it "handles the 'max_memory_bytes' command" do
125
+ response = send_request({ cmd: "max_memory_bytes" })
126
+ expect(response).to eq({ ok: true, value: 456 })
127
+ expect(Mudis).to have_received(:max_memory_bytes)
128
+ end
129
+
130
+ it "handles the 'metrics' command" do
131
+ response = send_request({ cmd: "metrics" })
132
+ expect(response).to eq({ ok: true, value: { reads: 1, writes: 1 } })
133
+ expect(Mudis).to have_received(:metrics)
100
134
  end
101
135
 
102
136
  it "handles unknown commands" do
data/spec/mudis_spec.rb CHANGED
@@ -55,6 +55,19 @@ RSpec.describe Mudis do # rubocop:disable Metrics/BlockLength
55
55
  Mudis.update("counter") { |v| v + 1 }
56
56
  expect(Mudis.read("counter")).to eq(6)
57
57
  end
58
+
59
+ it "refreshes TTL based on original duration" do
60
+ Mudis.write("ttl_key", "v", expires_in: 2)
61
+ meta_before = Mudis.inspect("ttl_key")
62
+ original_ttl = meta_before[:expires_at] - meta_before[:created_at]
63
+
64
+ sleep 1
65
+ Mudis.update("ttl_key") { |v| v }
66
+ meta_after = Mudis.inspect("ttl_key")
67
+
68
+ expect(meta_after[:created_at]).to be > meta_before[:created_at]
69
+ expect(meta_after[:expires_at]).to be_within(0.5).of(Time.now + original_ttl)
70
+ end
58
71
  end
59
72
 
60
73
  describe ".fetch" do
@@ -76,6 +89,30 @@ RSpec.describe Mudis do # rubocop:disable Metrics/BlockLength
76
89
  result = Mudis.fetch("k", force: true) { 200 } # fix
77
90
  expect(result).to eq(200)
78
91
  end
92
+
93
+ it "executes the block once with singleflight: true" do
94
+ Mudis.delete("sf")
95
+ count = 0
96
+ count_mutex = Mutex.new
97
+ results = []
98
+ results_mutex = Mutex.new
99
+
100
+ threads = 5.times.map do
101
+ Thread.new do
102
+ value = Mudis.fetch("sf", singleflight: true) do
103
+ count_mutex.synchronize { count += 1 }
104
+ sleep 0.05
105
+ "v"
106
+ end
107
+ results_mutex.synchronize { results << value }
108
+ end
109
+ end
110
+
111
+ threads.each(&:join)
112
+ expect(count).to eq(1)
113
+ expect(results).to all(eq("v"))
114
+ expect(Mudis.read("sf")).to eq("v")
115
+ end
79
116
  end
80
117
 
81
118
  describe ".clear" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mudis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.9.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - kiebor81
@@ -60,6 +60,7 @@ executables: []
60
60
  extensions: []
61
61
  extra_rdoc_files:
62
62
  - sig/mudis.rbs
63
+ - sig/mudis_bound.rbs
63
64
  - sig/mudis_client.rbs
64
65
  - sig/mudis_config.rbs
65
66
  - sig/mudis_expiry.rbs
@@ -72,6 +73,7 @@ extra_rdoc_files:
72
73
  files:
73
74
  - README.md
74
75
  - lib/mudis.rb
76
+ - lib/mudis/bound.rb
75
77
  - lib/mudis/expiry.rb
76
78
  - lib/mudis/lru.rb
77
79
  - lib/mudis/metrics.rb
@@ -84,6 +86,7 @@ files:
84
86
  - lib/mudis_proxy.rb
85
87
  - lib/mudis_server.rb
86
88
  - sig/mudis.rbs
89
+ - sig/mudis_bound.rbs
87
90
  - sig/mudis_client.rbs
88
91
  - sig/mudis_config.rbs
89
92
  - sig/mudis_expiry.rbs
@@ -94,6 +97,7 @@ files:
94
97
  - sig/mudis_persistence.rbs
95
98
  - sig/mudis_server.rbs
96
99
  - spec/api_compatibility_spec.rb
100
+ - spec/bound_spec.rb
97
101
  - spec/eviction_spec.rb
98
102
  - spec/guardrails_spec.rb
99
103
  - spec/memory_guard_spec.rb
@@ -121,7 +125,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
121
125
  requirements:
122
126
  - - ">="
123
127
  - !ruby/object:Gem::Version
124
- version: '3.0'
128
+ version: '3.3'
125
129
  required_rubygems_version: !ruby/object:Gem::Requirement
126
130
  requirements:
127
131
  - - ">="
@@ -135,6 +139,7 @@ summary: A fast in-memory, thread-safe and high performance Ruby LRU cache with
135
139
  and auto-expiry.
136
140
  test_files:
137
141
  - spec/api_compatibility_spec.rb
142
+ - spec/bound_spec.rb
138
143
  - spec/eviction_spec.rb
139
144
  - spec/guardrails_spec.rb
140
145
  - spec/memory_guard_spec.rb