From 0db76004aa97433297966ee73e9d690021b7c86f Mon Sep 17 00:00:00 2001 From: "Thomas, Jason M (Software)" Date: Wed, 3 Feb 2021 08:58:39 -0700 Subject: [PATCH 1/3] Fix hmset exception and xadd spec errors --- lib/mock_redis/hash_methods.rb | 2 +- spec/commands/xadd_spec.rb | 12 ++++++------ spec/commands/xlen_spec.rb | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/mock_redis/hash_methods.rb b/lib/mock_redis/hash_methods.rb index eeb83d7b..688976d2 100644 --- a/lib/mock_redis/hash_methods.rb +++ b/lib/mock_redis/hash_methods.rb @@ -94,7 +94,7 @@ def hmset(key, *kvpairs) assert_has_args(kvpairs, 'hmset') if kvpairs.length.odd? - raise Redis::CommandError, err_msg || 'ERR wrong number of arguments for HMSET' + raise Redis::CommandError, err_msg || 'ERR wrong number of arguments for \'hmset\' command' end kvpairs.each_slice(2) do |(k, v)| diff --git a/spec/commands/xadd_spec.rb b/spec/commands/xadd_spec.rb index 729edd82..ff299fc0 100644 --- a/spec/commands/xadd_spec.rb +++ b/spec/commands/xadd_spec.rb @@ -14,20 +14,20 @@ it 'returns an id based on the timestamp' do t = Time.now.to_i - id = @redises.xadd(@key, key: 'value') - expect(@redises.xadd(@key, key: 'value')).to match(/#{t}\d{3}-0/) + id = @redises.xadd(@key, { key: 'value' }) + expect(@redises.xadd(@key, { key: 'value' })).to match(/#{t}\d{3}-0/) end it 'adds data with symbols' do - @redises.xadd(@key, symbol_key: :symbol_value) + @redises.xadd(@key, { symbol_key: :symbol_value }) expect(@redises.xrange(@key, '-', '+').last[1]) .to eq('symbol_key' => 'symbol_value') end it 'increments the sequence number with the same timestamp' do Timecop.freeze do - @redises.xadd(@key, key: 'value') - expect(@redises.xadd(@key, key: 'value')).to match(/\d+-1/) + @redises.xadd(@key, { key: 'value' }) + expect(@redises.xadd(@key, { key: 'value' })).to match(/\d+-1/) end end @@ -67,7 +67,7 @@ it 'caters for the current time being before the last time' do t = (Time.now.to_f * 1000).to_i + 2000 @redises.xadd(@key, { key: 'value' }, id: "#{t}-0") - expect(@redises.xadd(@key, key: 'value')).to match(/#{t}-1/) + expect(@redises.xadd(@key, { key: 'value' })).to match(/#{t}-1/) end it 'appends a sequence number if it is missing' do diff --git a/spec/commands/xlen_spec.rb b/spec/commands/xlen_spec.rb index 5fe8d43e..eeaa509a 100644 --- a/spec/commands/xlen_spec.rb +++ b/spec/commands/xlen_spec.rb @@ -14,9 +14,9 @@ it 'returns the number of items in the stream' do expect(@redises.xlen(@key)).to eq 0 - @redises.xadd(@key, key: 'value') + @redises.xadd(@key, { key: 'value' }) expect(@redises.xlen(@key)).to eq 1 - 3.times { @redises.xadd(@key, key: 'value') } + 3.times { @redises.xadd(@key, { key: 'value' }) } expect(@redises.xlen(@key)).to eq 4 end end From 4b26efee4ecb49fe989597ca34043065be330441 Mon Sep 17 00:00:00 2001 From: "Thomas, Jason M (Software)" Date: Thu, 29 Apr 2021 07:54:38 -0600 Subject: [PATCH 2/3] Update ID regex --- lib/mock_redis/stream/id.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/mock_redis/stream/id.rb b/lib/mock_redis/stream/id.rb index c70ea2a1..7cca87fe 100644 --- a/lib/mock_redis/stream/id.rb +++ b/lib/mock_redis/stream/id.rb @@ -20,8 +20,11 @@ def initialize(id, min: nil, sequence: 0) @timestamp = @sequence = Float::INFINITY else if id.is_a? String - (_, @timestamp, @sequence) = id.match(/^(\d+)-?(\d+)?$/) - .to_a + # See https://redis.io/topics/streams-intro + # Ids are a unix timestamp in milliseconds followed by an + # optional dash sequence number, e.g. -0. They can also optionally + # be prefixed with '(' to change the XRANGE to exclusive. + (_, @timestamp, @sequence) = id.match(/^\(?(\d+)-?(\d+)?$/).to_a if @timestamp.nil? raise Redis::CommandError, 'ERR Invalid stream ID specified as stream command argument' From 30a27f3323faeff85d78e0585651c8f8d9d3b033 Mon Sep 17 00:00:00 2001 From: "Thomas, Jason M (Software)" Date: Fri, 30 Apr 2021 08:47:35 -0600 Subject: [PATCH 3/3] Implement exclusive xrange --- lib/mock_redis/stream.rb | 12 +++++++++--- lib/mock_redis/stream/id.rb | 4 +++- spec/commands/xrange_spec.rb | 16 +++++++++++++--- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/lib/mock_redis/stream.rb b/lib/mock_redis/stream.rb index 997c580c..9ce5f9c7 100644 --- a/lib/mock_redis/stream.rb +++ b/lib/mock_redis/stream.rb @@ -49,9 +49,15 @@ def range(start, finish, reversed, *opts_in) opts = options opts_in, ['count'] start_id = MockRedis::Stream::Id.new(start) finish_id = MockRedis::Stream::Id.new(finish, sequence: Float::INFINITY) - items = members - .select { |m| (start_id <= m[0]) && (finish_id >= m[0]) } - .map { |m| [m[0].to_s, m[1]] } + if start_id.exclusive + items = members + .select { |m| (start_id < m[0]) && (finish_id >= m[0]) } + .map { |m| [m[0].to_s, m[1]] } + else + items = members + .select { |m| (start_id <= m[0]) && (finish_id >= m[0]) } + .map { |m| [m[0].to_s, m[1]] } + end items.reverse! if reversed return items.first(opts['count'].to_i) if opts.key?('count') items diff --git a/lib/mock_redis/stream/id.rb b/lib/mock_redis/stream/id.rb index 7cca87fe..8f0bb2ad 100644 --- a/lib/mock_redis/stream/id.rb +++ b/lib/mock_redis/stream/id.rb @@ -3,9 +3,10 @@ class Stream class Id include Comparable - attr_accessor :timestamp, :sequence + attr_accessor :timestamp, :sequence, :exclusive def initialize(id, min: nil, sequence: 0) + @exclusive = false case id when '*' @timestamp = (Time.now.to_f * 1000).to_i @@ -25,6 +26,7 @@ def initialize(id, min: nil, sequence: 0) # optional dash sequence number, e.g. -0. They can also optionally # be prefixed with '(' to change the XRANGE to exclusive. (_, @timestamp, @sequence) = id.match(/^\(?(\d+)-?(\d+)?$/).to_a + @exclusive = true if id[0] == '(' if @timestamp.nil? raise Redis::CommandError, 'ERR Invalid stream ID specified as stream command argument' diff --git a/spec/commands/xrange_spec.rb b/spec/commands/xrange_spec.rb index a949294e..9111feeb 100644 --- a/spec/commands/xrange_spec.rb +++ b/spec/commands/xrange_spec.rb @@ -79,13 +79,23 @@ ) end - it 'returns entries with both a lower and an upper limit' do - expect(@redises.xrange(@key, '1234567891239-0', '1234567891285-0')).to eq( + it 'returns entries with both a lower and an upper limit inclusive' do + expect(@redises.xrange(@key, '1234567891245-0', '1234567891278-0')).to eq( [ ['1234567891245-0', { 'key2' => 'value2' }], ['1234567891245-1', { 'key3' => 'value3' }], + ['1234567891278-0', { 'key4' => 'value4' }] + ] + ) + end + + it 'returns entries with both a lower and an upper limit exclusive' do + expect(@redises.xrange(@key, '(1234567891245-0', '1234567891285-1')).to eq( + [ + # We no longer get '1234567891245-0' + ['1234567891245-1', { 'key3' => 'value3' }], ['1234567891278-0', { 'key4' => 'value4' }], - ['1234567891278-1', { 'key5' => 'value5' }] + ['1234567891278-1', { 'key5' => 'value5' }] # Note sequence -1 ] ) end