Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
647 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
require 'forwardable' | ||
require 'set' | ||
require 'date' | ||
require 'mock_redis/stream/id' | ||
|
||
# TODO: Implement the following commands | ||
# | ||
# * xread | ||
# * xgroup | ||
# * xreadgroup | ||
# * xack | ||
# * xpending | ||
# * xclaim | ||
# * xinfo | ||
# * xtrim | ||
# * xdel | ||
# | ||
# For details of these commands see https://redis.io/topics/streams-intro | ||
|
||
class MockRedis | ||
class Stream | ||
include Enumerable | ||
extend Forwardable | ||
|
||
attr_accessor :members | ||
|
||
def_delegators :members, :empty? | ||
|
||
def initialize | ||
@members = Set.new | ||
@last_id = nil | ||
end | ||
|
||
def last_id | ||
@last_id.to_s | ||
end | ||
|
||
def add(id, values) | ||
@last_id = MockRedis::Stream::Id.new(id, min: @last_id) | ||
members.add [@last_id, values.map(&:to_s)] | ||
@last_id.to_s | ||
end | ||
|
||
def range(start, finish, reversed, *opts_in) | ||
opts = options opts_in, ['count'] | ||
unless opts['count'].nil? || /^\d*$/.match(opts['count']) | ||
raise Redis::CommandError, 'ERR value is not an integer or out of range' | ||
end | ||
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]] } | ||
items.reverse! if reversed | ||
return items.first(opts['count'].to_i) if opts.key?('count') | ||
items | ||
end | ||
|
||
def each | ||
members.each { |m| yield m } | ||
end | ||
|
||
private | ||
|
||
def options(opts_in, permitted) | ||
opts_out = {} | ||
raise Redis::CommandError, 'ERR syntax error' unless (opts_in.length % 2).zero? | ||
opts_in.each_slice(2).map { |pair| opts_out[pair[0].downcase] = pair[1] } | ||
raise Redis::CommandError, 'ERR syntax error' unless (opts_out.keys - permitted).empty? | ||
opts_out | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
class MockRedis | ||
class Stream | ||
class Id | ||
include Comparable | ||
|
||
attr_accessor :timestamp, :sequence | ||
|
||
def initialize(id, min: nil, sequence: 0) | ||
case id | ||
when '*' | ||
@timestamp = DateTime.now.strftime('%Q').to_i | ||
@sequence = 0 | ||
if self <= min | ||
@timestamp = min.timestamp | ||
@sequence = min.sequence + 1 | ||
end | ||
when '-' | ||
@timestamp = @sequence = 0 | ||
when '+' | ||
@timestamp = @sequence = Float::INFINITY | ||
else | ||
if id.is_a? String | ||
(_, @timestamp, @sequence) = id.match(/^(\d+)-?(\d+)?$/) | ||
.to_a | ||
if @timestamp.nil? | ||
raise Redis::CommandError, | ||
'ERR Invalid stream ID specified as stream command argument' | ||
end | ||
@timestamp = @timestamp.to_i | ||
else | ||
@timestamp = id | ||
end | ||
@sequence = @sequence.nil? ? sequence : @sequence.to_i | ||
if self <= min | ||
raise Redis::CommandError, | ||
'ERR The ID specified in XADD is equal or smaller than ' \ | ||
'the target stream top item' | ||
end | ||
end | ||
end | ||
|
||
def to_s | ||
"#{@timestamp}-#{@sequence}" | ||
end | ||
|
||
def <=>(other) | ||
return 1 if other.nil? | ||
return @sequence <=> other.sequence if @timestamp == other.timestamp | ||
@timestamp <=> other.timestamp | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
require 'mock_redis/assertions' | ||
require 'mock_redis/utility_methods' | ||
require 'mock_redis/stream' | ||
|
||
class MockRedis | ||
module StreamMethods | ||
include Assertions | ||
include UtilityMethods | ||
|
||
def xadd(key = nil, id = nil, *args) | ||
if args.count == 0 | ||
raise Redis::CommandError, | ||
"ERR wrong number of arguments for 'xadd' command" | ||
end | ||
if args.count.odd? | ||
raise Redis::CommandError, | ||
'ERR wrong number of arguments for XADD' | ||
end | ||
with_stream_at(key) do |stream| | ||
stream.add id, args | ||
return stream.last_id | ||
end | ||
end | ||
|
||
def xlen(key = nil, *args) | ||
if key.nil? || args.count > 0 | ||
raise Redis::CommandError, | ||
"ERR wrong number of arguments for 'xlen' command" | ||
end | ||
with_stream_at(key) do |stream| | ||
return stream.count | ||
end | ||
end | ||
|
||
def xrange(key = nil, start = nil, finish = nil, *options) | ||
if finish.nil? | ||
raise Redis::CommandError, | ||
"ERR wrong number of arguments for 'xrange' command" | ||
end | ||
with_stream_at(key) do |stream| | ||
return stream.range(start, finish, false, *options) | ||
end | ||
end | ||
|
||
def xrevrange(key = nil, finish = nil, start = nil, *options) | ||
if start.nil? | ||
raise Redis::CommandError, | ||
"ERR wrong number of arguments for 'xrevrange' command" | ||
end | ||
with_stream_at(key) do |stream| | ||
return stream.range(start, finish, true, *options) | ||
end | ||
end | ||
|
||
private | ||
|
||
def with_stream_at(key, &blk) | ||
with_thing_at(key, :assert_streamy, proc { Stream.new }, &blk) | ||
end | ||
|
||
def streamy?(key) | ||
data[key].nil? || data[key].is_a?(Stream) | ||
end | ||
|
||
def assert_streamy(key) | ||
unless streamy?(key) | ||
raise Redis::CommandError, | ||
'WRONGTYPE Operation against a key holding the wrong kind of value' | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
require 'spec_helper' | ||
|
||
describe '#xadd(key, id, [field, value, ...])' do | ||
before { @key = 'mock-redis-test:xadd' } | ||
|
||
it 'returns an id based on the timestamp' do | ||
t = Time.now.to_i | ||
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) | ||
expect(@redises.xrange(@key, '-', '+').last[1]) | ||
.to eq(%w[symbol_key symbol_value]) | ||
end | ||
|
||
it 'increments the sequence number with the same timestamp' do | ||
@redises.xadd(@key, '*', 'key', 'value') | ||
expect(@redises.xadd(@key, '*', 'key', 'value')).to match(/\d+-1/) | ||
end | ||
|
||
it 'sets the id if it is given' do | ||
expect(@redises.xadd(@key, '1234567891234-2', 'key', 'value')) | ||
.to eq '1234567891234-2' | ||
end | ||
|
||
it 'accepts is as an integer' do | ||
expect(@redises.xadd(@key, 1_234_567_891_234, 'key', 'value')) | ||
.to eq '1234567891234-0' | ||
end | ||
|
||
it 'sets an id based on the timestamp if the given id is before the last' do | ||
@redises.xadd(@key, '1234567891234-0', 'key', 'value') | ||
expect { @redises.xadd(@key, '1234567891233-0', 'key', 'value') } | ||
.to raise_error( | ||
Redis::CommandError, | ||
'ERR The ID specified in XADD is equal or smaller than the target ' \ | ||
'stream top item' | ||
) | ||
end | ||
|
||
it 'caters for the current time being before the last time' do | ||
t = DateTime.now.strftime('%Q').to_i + 2000 | ||
@redises.xadd(@key, "#{t}-0", 'key', 'value') | ||
expect(@redises.xadd(@key, '*', 'key', 'value')).to match(/#{t}-1/) | ||
end | ||
|
||
it 'appends a sequence number if it is missing' do | ||
expect(@redises.xadd(@key, '1234567891234', 'key', 'value')) | ||
.to eq '1234567891234-0' | ||
end | ||
|
||
it 'raises wrong number of arguments error with missing values' do | ||
expect { @redises.xadd(@key, '*') } | ||
.to raise_error( | ||
Redis::CommandError, | ||
"ERR wrong number of arguments for 'xadd' command" | ||
) | ||
end | ||
|
||
it 'raises wrong number of arguments error with odd number of values' do | ||
expect { @redises.xadd(@key, '*', 'key', 'value', 'key') } | ||
.to raise_error( | ||
Redis::CommandError, | ||
'ERR wrong number of arguments for XADD' | ||
) | ||
end | ||
|
||
it 'raises an invalid stream id error' do | ||
expect { @redises.xadd(@key, 'X', 'key', 'value') } | ||
.to raise_error( | ||
Redis::CommandError, | ||
'ERR Invalid stream ID specified as stream command argument' | ||
) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
require 'spec_helper' | ||
|
||
describe '#xlen(key)' do | ||
before { @key = 'mock-redis-test:xlen' } | ||
|
||
it 'returns the number of items in the stream' do | ||
expect(@redises.xlen(@key)).to eq 0 | ||
@redises.xadd(@key, '*', 'key', 'value') | ||
expect(@redises.xlen(@key)).to eq 1 | ||
3.times { @redises.xadd(@key, '*', 'key', 'value') } | ||
expect(@redises.xlen(@key)).to eq 4 | ||
end | ||
|
||
it 'raises wrong number of arguments error with missing key' do | ||
expect { @redises.xlen } | ||
.to raise_error( | ||
Redis::CommandError, | ||
"ERR wrong number of arguments for 'xlen' command" | ||
) | ||
end | ||
|
||
it 'raises wrong number of arguments error with extra arguments' do | ||
expect { @redises.xlen(@key, 'xyz') } | ||
.to raise_error( | ||
Redis::CommandError, | ||
"ERR wrong number of arguments for 'xlen' command" | ||
) | ||
end | ||
end |
Oops, something went wrong.