From f5fa0869f16043cc4f347da1682fc155cb3244a6 Mon Sep 17 00:00:00 2001 From: Patrick Chieppe Date: Thu, 14 Nov 2024 17:42:37 +1100 Subject: [PATCH 01/10] Implement lmpop --- lib/mock_redis/list_methods.rb | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/mock_redis/list_methods.rb b/lib/mock_redis/list_methods.rb index 69c338b..4b1d9b8 100644 --- a/lib/mock_redis/list_methods.rb +++ b/lib/mock_redis/list_methods.rb @@ -90,6 +90,30 @@ def llen(key) with_list_at(key, &:length) end + def lmpop(*args, **options) + keys.each do |key| + assert_listy(key) + end + + modifier = options.is_a?(Hash) && options[:timeout]&.to_s&.downcase || 'left' + count = (options.is_a?(Hash) && options[:count]) || 1 + + unless %w[left right].include?(modifier) + raise Redis::CommandError, 'ERR syntax error' + end + + keys.each do |key| + record_count = llen(key) + next if record_count.zero? + + [count, record_count].min.times.map do + modifier == 'left' ? with_list_at(key, &:shift) : with_list_at(key, &:pop) + end + end + + nil + end + def lmove(source, destination, wherefrom, whereto) assert_listy(source) assert_listy(destination) From 7d9d8d70eb6a45b5a9b36d8bad149ee11e53dec4 Mon Sep 17 00:00:00 2001 From: Patrick Chieppe Date: Thu, 14 Nov 2024 17:58:32 +1100 Subject: [PATCH 02/10] Add lmpop specs --- spec/commands/lmpop_spec.rb | 43 +++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 spec/commands/lmpop_spec.rb diff --git a/spec/commands/lmpop_spec.rb b/spec/commands/lmpop_spec.rb new file mode 100644 index 0000000..6259abc --- /dev/null +++ b/spec/commands/lmpop_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +RSpec.describe '#lmpop(*keys)' do + before do + @list1 = 'mock-redis-test:lmpop-list' + @list2 = 'mock-redis-test:lmpop-list2' + + @redises.lpush(@list1, 'b') + @redises.lpush(@list1, 'a') + + @redises.lpush(@list2, 'y') + @redises.lpush(@list2, 'x') + end + + it 'returns and removes the first element of the first non-empty list' do + expect(@redises.lmpop('empty', @list1, @list2)).to eq([@list2, ['a']]) + + expect(@redises.lrange(@list1, 0, -1)).to eq(%w[b]) + expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y]) + end + + it 'returns falsed if all lists are empty' do + expect(@redises.lmpop('empty')).to be_nil + + expect(@redises.lrange(@list1, 0, -1)).to eq(%w[a b]) + expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y]) + end + + it 'removes empty lists' do + @redises.llen(@list1).times { @redises.lmpop(@list1, @list2) } + expect(@redises.get(@list1)).to be_nil + end + + it 'raises an error for non-list source value' do + @redises.set(@list1, 'string value') + + expect do + @redises.lmpop(@list1, @list2) + end.to raise_error(Redis::CommandError) + end + + it_should_behave_like 'a list-only command' +end From bf9f38e6e8561500ac7ed8780895e24283a179c8 Mon Sep 17 00:00:00 2001 From: Patrick Chieppe Date: Thu, 14 Nov 2024 18:11:11 +1100 Subject: [PATCH 03/10] Fix incorrect argument name --- lib/mock_redis/list_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mock_redis/list_methods.rb b/lib/mock_redis/list_methods.rb index 4b1d9b8..47b945a 100644 --- a/lib/mock_redis/list_methods.rb +++ b/lib/mock_redis/list_methods.rb @@ -90,7 +90,7 @@ def llen(key) with_list_at(key, &:length) end - def lmpop(*args, **options) + def lmpop(*keys, **options) keys.each do |key| assert_listy(key) end From ea7c83c901035c05f57de8d428b0722426bf4cee Mon Sep 17 00:00:00 2001 From: Patrick Chieppe Date: Thu, 14 Nov 2024 18:15:02 +1100 Subject: [PATCH 04/10] Add missing return --- lib/mock_redis/list_methods.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/mock_redis/list_methods.rb b/lib/mock_redis/list_methods.rb index 47b945a..785b43d 100644 --- a/lib/mock_redis/list_methods.rb +++ b/lib/mock_redis/list_methods.rb @@ -106,9 +106,11 @@ def lmpop(*keys, **options) record_count = llen(key) next if record_count.zero? - [count, record_count].min.times.map do + values = [count, record_count].min.times.map do modifier == 'left' ? with_list_at(key, &:shift) : with_list_at(key, &:pop) end + + return [key, values] end nil From 7ed700000dd8a600011e424467e78ecdb4a0fdea Mon Sep 17 00:00:00 2001 From: Patrick Chieppe Date: Fri, 15 Nov 2024 11:14:48 +1100 Subject: [PATCH 05/10] Fix wrong options key --- lib/mock_redis/list_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mock_redis/list_methods.rb b/lib/mock_redis/list_methods.rb index 785b43d..7f7f993 100644 --- a/lib/mock_redis/list_methods.rb +++ b/lib/mock_redis/list_methods.rb @@ -95,7 +95,7 @@ def lmpop(*keys, **options) assert_listy(key) end - modifier = options.is_a?(Hash) && options[:timeout]&.to_s&.downcase || 'left' + modifier = options.is_a?(Hash) && options[:modifier]&.to_s&.downcase || 'left' count = (options.is_a?(Hash) && options[:count]) || 1 unless %w[left right].include?(modifier) From 83bc1d0a22b4c50aab522359597be16657a3ae54 Mon Sep 17 00:00:00 2001 From: Patrick Chieppe Date: Fri, 15 Nov 2024 11:23:20 +1100 Subject: [PATCH 06/10] Add specs for lmpop with modifier and count --- spec/commands/lmpop_spec.rb | 40 ++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/spec/commands/lmpop_spec.rb b/spec/commands/lmpop_spec.rb index 6259abc..9857785 100644 --- a/spec/commands/lmpop_spec.rb +++ b/spec/commands/lmpop_spec.rb @@ -5,9 +5,11 @@ @list1 = 'mock-redis-test:lmpop-list' @list2 = 'mock-redis-test:lmpop-list2' + @redises.lpush(@list1, 'c') @redises.lpush(@list1, 'b') @redises.lpush(@list1, 'a') + @redises.lpush(@list2, 'z') @redises.lpush(@list2, 'y') @redises.lpush(@list2, 'x') end @@ -15,19 +17,47 @@ it 'returns and removes the first element of the first non-empty list' do expect(@redises.lmpop('empty', @list1, @list2)).to eq([@list2, ['a']]) - expect(@redises.lrange(@list1, 0, -1)).to eq(%w[b]) - expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y]) + expect(@redises.lrange(@list1, 0, -1)).to eq(%w[b c]) + expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y z]) + end + + it 'returns and removes the first element of the first non-empty list when modifier is LEFT' do + expect(@redises.lmpop('empty', @list1, @list2), modifier: 'LEFT').to eq([@list2, ['a']]) + + expect(@redises.lrange(@list1, 0, -1)).to eq(%w[b c]) + expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y z]) + end + + it 'returns and removes the last element of the first non-empty list when modifier is RIGHT' do + expect(@redises.lmpop('empty', @list1, @list2, modifier: 'RIGHT')).to eq([@list2, ['c']]) + + expect(@redises.lrange(@list1, 0, -1)).to eq(%w[a b]) + expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y z]) + end + + it 'returns and removes multiple elements from the front when count is given' do + expect(@redises.lmpop('empty', @list1, @list2, count: 2)).to eq([@list2, ['a', 'b']]) + + expect(@redises.lrange(@list1, 0, -1)).to eq(%w[c]) + expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y z]) + end + + it 'returns and removes multiple elements from the back when count is given and modifier is RIGHT' do + expect(@redises.lmpop('empty', @list1, @list2, count: 2, modifier: 'RIGHT')).to eq([@list2, ['c', 'b']]) + + expect(@redises.lrange(@list1, 0, -1)).to eq(%w[a]) + expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y z]) end it 'returns falsed if all lists are empty' do expect(@redises.lmpop('empty')).to be_nil - expect(@redises.lrange(@list1, 0, -1)).to eq(%w[a b]) - expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y]) + expect(@redises.lrange(@list1, 0, -1)).to eq(%w[a b c]) + expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y c]) end it 'removes empty lists' do - @redises.llen(@list1).times { @redises.lmpop(@list1, @list2) } + (@redises.llen(@list1) + @redises.llen(@list2)).times { @redises.lmpop(@list1, @list2) } expect(@redises.get(@list1)).to be_nil end From 1361347f4dd80cb9b6b5971619f5234b34cdb828 Mon Sep 17 00:00:00 2001 From: Patrick Chieppe Date: Wed, 27 Nov 2024 11:51:08 +1100 Subject: [PATCH 07/10] Fix lmpop tests --- spec/commands/lmpop_spec.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/commands/lmpop_spec.rb b/spec/commands/lmpop_spec.rb index 9857785..502fcc5 100644 --- a/spec/commands/lmpop_spec.rb +++ b/spec/commands/lmpop_spec.rb @@ -15,35 +15,35 @@ end it 'returns and removes the first element of the first non-empty list' do - expect(@redises.lmpop('empty', @list1, @list2)).to eq([@list2, ['a']]) + expect(@redises.lmpop('empty', @list1, @list2)).to eq([@list1, ['a']]) expect(@redises.lrange(@list1, 0, -1)).to eq(%w[b c]) expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y z]) end it 'returns and removes the first element of the first non-empty list when modifier is LEFT' do - expect(@redises.lmpop('empty', @list1, @list2), modifier: 'LEFT').to eq([@list2, ['a']]) + expect(@redises.lmpop('empty', @list1, @list2, modifier: 'LEFT')).to eq([@list1, ['a']]) expect(@redises.lrange(@list1, 0, -1)).to eq(%w[b c]) expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y z]) end it 'returns and removes the last element of the first non-empty list when modifier is RIGHT' do - expect(@redises.lmpop('empty', @list1, @list2, modifier: 'RIGHT')).to eq([@list2, ['c']]) + expect(@redises.lmpop('empty', @list1, @list2, modifier: 'RIGHT')).to eq([@list1, ['c']]) expect(@redises.lrange(@list1, 0, -1)).to eq(%w[a b]) expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y z]) end it 'returns and removes multiple elements from the front when count is given' do - expect(@redises.lmpop('empty', @list1, @list2, count: 2)).to eq([@list2, ['a', 'b']]) + expect(@redises.lmpop('empty', @list1, @list2, count: 2)).to eq([@list1, ['a', 'b']]) expect(@redises.lrange(@list1, 0, -1)).to eq(%w[c]) expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y z]) end it 'returns and removes multiple elements from the back when count is given and modifier is RIGHT' do - expect(@redises.lmpop('empty', @list1, @list2, count: 2, modifier: 'RIGHT')).to eq([@list2, ['c', 'b']]) + expect(@redises.lmpop('empty', @list1, @list2, count: 2, modifier: 'RIGHT')).to eq([@list1, ['c', 'b']]) expect(@redises.lrange(@list1, 0, -1)).to eq(%w[a]) expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y z]) @@ -53,7 +53,7 @@ expect(@redises.lmpop('empty')).to be_nil expect(@redises.lrange(@list1, 0, -1)).to eq(%w[a b c]) - expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y c]) + expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y z]) end it 'removes empty lists' do From 885fcb33ef3891307c6c79d199c46c685b9f1475 Mon Sep 17 00:00:00 2001 From: Patrick Chieppe Date: Wed, 27 Nov 2024 12:00:57 +1100 Subject: [PATCH 08/10] Mark lmpop specs as redis 7.0 --- spec/commands/lmpop_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/commands/lmpop_spec.rb b/spec/commands/lmpop_spec.rb index 502fcc5..5a023d8 100644 --- a/spec/commands/lmpop_spec.rb +++ b/spec/commands/lmpop_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -RSpec.describe '#lmpop(*keys)' do +RSpec.describe '#lmpop(*keys)', redis: 7.0 do before do @list1 = 'mock-redis-test:lmpop-list' @list2 = 'mock-redis-test:lmpop-list2' From 7cf1f6dba41618ffa52ae6a626b92bc74e73c9e6 Mon Sep 17 00:00:00 2001 From: Patrick Chieppe Date: Wed, 27 Nov 2024 12:05:07 +1100 Subject: [PATCH 09/10] Add base64 to Gemfile --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index 16f3338..430f85a 100644 --- a/Gemfile +++ b/Gemfile @@ -9,5 +9,6 @@ gem 'overcommit', '0.62.0' # Pin tool versions (which are executed by Overcommit) for CI builds gem 'rubocop', '1.44.1' +gem 'base64', '~> 0.2.0' gem 'simplecov', '~> 0.22.0' gem 'simplecov-lcov', '~> 0.8.0' From 1940c89bc104e9a5a74cc8f3643a425cdce78f83 Mon Sep 17 00:00:00 2001 From: Patrick Chieppe Date: Wed, 27 Nov 2024 12:07:29 +1100 Subject: [PATCH 10/10] Fix lmpop lints --- spec/commands/lmpop_spec.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/spec/commands/lmpop_spec.rb b/spec/commands/lmpop_spec.rb index 5a023d8..d7f753e 100644 --- a/spec/commands/lmpop_spec.rb +++ b/spec/commands/lmpop_spec.rb @@ -36,14 +36,16 @@ end it 'returns and removes multiple elements from the front when count is given' do - expect(@redises.lmpop('empty', @list1, @list2, count: 2)).to eq([@list1, ['a', 'b']]) + expect(@redises.lmpop('empty', @list1, @list2, count: 2)).to eq([@list1, %w[a b]]) expect(@redises.lrange(@list1, 0, -1)).to eq(%w[c]) expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y z]) end - it 'returns and removes multiple elements from the back when count is given and modifier is RIGHT' do - expect(@redises.lmpop('empty', @list1, @list2, count: 2, modifier: 'RIGHT')).to eq([@list1, ['c', 'b']]) + it 'returns and removes multiple elements from the back when count given and modifier is RIGHT' do + expect(@redises.lmpop('empty', @list1, @list2, count: 2, modifier: 'RIGHT')).to( + eq([@list1, %w[c b]]) + ) expect(@redises.lrange(@list1, 0, -1)).to eq(%w[a]) expect(@redises.lrange(@list2, 0, -1)).to eq(%w[x y z])