mock_redis 0.19.0 → 0.20.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -1
  3. data/.travis.yml +9 -10
  4. data/CHANGELOG.md +15 -0
  5. data/Gemfile +2 -2
  6. data/README.md +2 -2
  7. data/lib/mock_redis/database.rb +6 -5
  8. data/lib/mock_redis/geospatial_methods.rb +10 -18
  9. data/lib/mock_redis/hash_methods.rb +4 -4
  10. data/lib/mock_redis/indifferent_hash.rb +0 -8
  11. data/lib/mock_redis/list_methods.rb +2 -2
  12. data/lib/mock_redis/pipelined_wrapper.rb +25 -6
  13. data/lib/mock_redis/set_methods.rb +15 -4
  14. data/lib/mock_redis/stream.rb +62 -0
  15. data/lib/mock_redis/stream/id.rb +53 -0
  16. data/lib/mock_redis/stream_methods.rb +87 -0
  17. data/lib/mock_redis/string_methods.rb +5 -8
  18. data/lib/mock_redis/transaction_wrapper.rb +25 -12
  19. data/lib/mock_redis/utility_methods.rb +1 -1
  20. data/lib/mock_redis/version.rb +1 -1
  21. data/lib/mock_redis/zset_methods.rb +2 -2
  22. data/mock_redis.gemspec +6 -5
  23. data/spec/commands/geodist_spec.rb +8 -4
  24. data/spec/commands/geohash_spec.rb +4 -4
  25. data/spec/commands/geopos_spec.rb +4 -4
  26. data/spec/commands/get_spec.rb +1 -0
  27. data/spec/commands/hdel_spec.rb +2 -2
  28. data/spec/commands/mget_spec.rb +34 -15
  29. data/spec/commands/mset_spec.rb +14 -0
  30. data/spec/commands/pipelined_spec.rb +52 -0
  31. data/spec/commands/spop_spec.rb +15 -0
  32. data/spec/commands/watch_spec.rb +8 -3
  33. data/spec/commands/xadd_spec.rb +102 -0
  34. data/spec/commands/xlen_spec.rb +20 -0
  35. data/spec/commands/xrange_spec.rb +141 -0
  36. data/spec/commands/xrevrange_spec.rb +130 -0
  37. data/spec/commands/xtrim_spec.rb +30 -0
  38. data/spec/spec_helper.rb +2 -0
  39. data/spec/support/redis_multiplexer.rb +17 -1
  40. data/spec/support/shared_examples/does_not_cleanup_empty_strings.rb +14 -0
  41. data/spec/support/shared_examples/only_operates_on_hashes.rb +2 -0
  42. data/spec/support/shared_examples/only_operates_on_lists.rb +2 -0
  43. data/spec/support/shared_examples/only_operates_on_sets.rb +2 -0
  44. data/spec/support/shared_examples/only_operates_on_zsets.rb +2 -0
  45. data/spec/transactions_spec.rb +17 -29
  46. metadata +38 -12
  47. data/spec/commands/hash_operator_spec.rb +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da2c2693b445e51b487e52f7c9b3986960417686792f9a5e554ca4d215c323e2
4
- data.tar.gz: 8da2170fcc49e768133a61deb716d72cc18d66291567008d96287b8d2104b15c
3
+ metadata.gz: 0e3dccfea232f8d47d28dc16362a79232cd69a2a12e6d8127a43c83c03287ef4
4
+ data.tar.gz: a2918811810246e6ddf92643e88121550310a7628f396aba12ed163b61c979a5
5
5
  SHA512:
6
- metadata.gz: 15b8b59cff1120de0befaee6cf08facfb120e239d3fbc1b6b55f40bb60c526f57e5cbb61f1d9f6fd5538067122e49b80e9e782590cd98070a3d49943ec8bb929
7
- data.tar.gz: 52a09f0896a405137be0075db930bec09898dd96e3d5366a95ef6a189268348f91244c051dbe45db5b4b37023a12f87552868fcaf2a5383316bebe9bdaf44997
6
+ metadata.gz: e8233e6e66afbaa2f7801e33f7b16a7bbbb2eb4cb3caab73a0c19e94857542fe49a96a9c45e8e995b54bf71d50c90ef78cfb5e293e85d91a3ade26f0e3a3e4d8
7
+ data.tar.gz: e30d5a2656ef3fb793cb9a0a2df9e1e24cd78aaa83a6cf5a3483353075f2a475379e294fc6b9c5905229db1a453d16cc7fa02095aed3ad59799312ff5d76ced5
@@ -1,7 +1,10 @@
1
1
  inherit_from: .rubocop_todo.yml
2
2
 
3
3
  AllCops:
4
- TargetRubyVersion: 2.2
4
+ TargetRubyVersion: 2.4
5
+
6
+ Layout/AlignArguments:
7
+ Enabled: false
5
8
 
6
9
  Layout/AlignParameters:
7
10
  Enabled: false
@@ -9,6 +12,9 @@ Layout/AlignParameters:
9
12
  Layout/DotPosition:
10
13
  Enabled: false
11
14
 
15
+ Layout/EmptyLineAfterGuardClause:
16
+ Enabled: false
17
+
12
18
  Lint/AssignmentInCondition:
13
19
  Enabled: false
14
20
 
@@ -1,7 +1,5 @@
1
1
  language: ruby
2
2
 
3
- sudo: false
4
-
5
3
  cache: bundler
6
4
 
7
5
  addons:
@@ -12,11 +10,16 @@ addons:
12
10
  services:
13
11
  - redis-server
14
12
 
13
+ before_install:
14
+ - sudo sed -e 's/^bind.*/bind 127.0.0.1/' /etc/redis/redis.conf > redis.conf
15
+ - sudo mv redis.conf /etc/redis
16
+ - sudo service redis-server start
17
+ - echo PING | nc localhost 6379
18
+
15
19
  rvm:
16
- - 2.2
17
- - 2.3.7
18
- - 2.4.4
19
- - 2.5.1
20
+ - 2.4.6
21
+ - 2.5.5
22
+ - 2.6.3
20
23
 
21
24
  before_script:
22
25
  - git config --local user.email "travis@travis.ci"
@@ -27,7 +30,3 @@ script:
27
30
  - bundle exec rspec
28
31
  - bundle exec overcommit --sign
29
32
  - bundle exec overcommit --run
30
-
31
- matrix:
32
- allow_failures:
33
- - rvm: 2.1.0
@@ -1,5 +1,20 @@
1
1
  # MockRedis Changelog
2
2
 
3
+ ### 0.20.0
4
+
5
+ * Add support for `count` parameter of `spop`
6
+ * Fix `mget` and `mset` to accept array as parameters
7
+ * Fix pipelined array replies
8
+ * Fix nested pipelining
9
+ * Allow nested multi
10
+ * Require Redis gem 4.0.1 or newer
11
+ * Add support for stream commands on Redis 5
12
+ * Keep empty strings on type mismatch
13
+ * Improve performance of `set_expiration`
14
+ * Fix `watch` to allow multiple keys
15
+ * Add `unlink` alias for `del`
16
+ * Drop support for Ruby 2.3 or older
17
+
3
18
  ### 0.19.0
4
19
 
5
20
  * Require Ruby 2.2+
data/Gemfile CHANGED
@@ -4,9 +4,9 @@ source 'http://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  # Run all pre-commit hooks via Overcommit during CI runs
7
- gem 'overcommit', '0.45.0'
7
+ gem 'overcommit', '0.48.0'
8
8
 
9
9
  # Pin tool versions (which are executed by Overcommit) for Travis builds
10
- gem 'rubocop', '0.58.2'
10
+ gem 'rubocop', '0.68.1'
11
11
 
12
12
  gem 'coveralls', require: false
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # MockRedis
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/mock_redis.svg)](http://badge.fury.io/rb/mock_redis)
4
- [![Build Status](https://travis-ci.org/brigade/mock_redis.svg)](https://travis-ci.org/brigade/mock_redis)
5
- [![Coverage Status](https://coveralls.io/repos/brigade/mock_redis/badge.svg)](https://coveralls.io/r/brigade/mock_redis)
4
+ [![Build Status](https://travis-ci.org/sds/mock_redis.svg)](https://travis-ci.org/sds/mock_redis)
5
+ [![Coverage Status](https://coveralls.io/repos/sds/mock_redis/badge.svg)](https://coveralls.io/r/sds/mock_redis)
6
6
 
7
7
  MockRedis provides the same interface as `redis-rb`, but it stores its
8
8
  data in memory instead of talking to a Redis server. It is intended
@@ -10,6 +10,7 @@ require 'mock_redis/indifferent_hash'
10
10
  require 'mock_redis/info_method'
11
11
  require 'mock_redis/utility_methods'
12
12
  require 'mock_redis/geospatial_methods'
13
+ require 'mock_redis/stream_methods'
13
14
 
14
15
  class MockRedis
15
16
  class Database
@@ -22,6 +23,7 @@ class MockRedis
22
23
  include InfoMethod
23
24
  include UtilityMethods
24
25
  include GeospatialMethods
26
+ include StreamMethods
25
27
 
26
28
  attr_reader :data, :expire_times
27
29
 
@@ -73,6 +75,7 @@ class MockRedis
73
75
  each { |k| data.delete(k) }.
74
76
  length
75
77
  end
78
+ alias unlink del
76
79
 
77
80
  def echo(msg)
78
81
  msg.to_s
@@ -303,11 +306,9 @@ class MockRedis
303
306
 
304
307
  def set_expiration(key, time)
305
308
  remove_expiration(key)
306
-
307
- expire_times << [time, key.to_s]
308
- expire_times.sort! do |a, b|
309
- a.first <=> b.first
310
- end
309
+ found = expire_times.each_with_index.to_a.bsearch { |item, _| item.first >= time }
310
+ index = found ? found.last : -1
311
+ expire_times.insert(index, [time, key.to_s])
311
312
  end
312
313
 
313
314
  def zero_pad(string, desired_length)
@@ -1,7 +1,7 @@
1
1
  class MockRedis
2
2
  module GeospatialMethods
3
- LNG_RANGE = (-180..180)
4
- LAT_RANGE = (-85.05112878..85.05112878)
3
+ LNG_RANGE = (-180..180).freeze
4
+ LAT_RANGE = (-85.05112878..85.05112878).freeze
5
5
  STEP = 26
6
6
  UNITS = {
7
7
  m: 1,
@@ -23,21 +23,13 @@ class MockRedis
23
23
  zadd(key, scored_points)
24
24
  end
25
25
 
26
- def geodist(key, *args)
27
- if args.length < 2
28
- raise Redis::CommandError,
29
- "ERR wrong number of arguments for 'geodist' command"
30
- end
31
-
32
- raise Redis::CommandError, 'ERR syntax error' if args.length > 3
33
-
34
- to_meter = 1
35
- to_meter = parse_unit(args[2]) if args.length == 3
26
+ def geodist(key, member1, member2, unit = 'm')
27
+ to_meter = parse_unit(unit)
36
28
 
37
29
  return nil if zcard(key).zero?
38
30
 
39
- score1 = zscore(key, args[0])
40
- score2 = zscore(key, args[1])
31
+ score1 = zscore(key, member1)
32
+ score2 = zscore(key, member2)
41
33
  return nil if score1.nil? || score2.nil?
42
34
  hash1 = { bits: score1.to_i, step: STEP }
43
35
  hash2 = { bits: score2.to_i, step: STEP }
@@ -49,12 +41,12 @@ class MockRedis
49
41
  format('%.4f', distance)
50
42
  end
51
43
 
52
- def geohash(key, *members)
44
+ def geohash(key, members)
53
45
  lng_range = (-180..180)
54
46
  lat_range = (-90..90)
55
47
  geoalphabet = '0123456789bcdefghjkmnpqrstuvwxyz'
56
48
 
57
- members.map do |member|
49
+ Array(members).map do |member|
58
50
  score = zscore(key, member)
59
51
  next nil unless score
60
52
  score = score.to_i
@@ -71,8 +63,8 @@ class MockRedis
71
63
  end
72
64
  end
73
65
 
74
- def geopos(key, *members)
75
- members.map do |member|
66
+ def geopos(key, members)
67
+ Array(members).map do |member|
76
68
  score = zscore(key, member)
77
69
  next nil unless score
78
70
  hash = { bits: score.to_i, step: STEP }
@@ -6,15 +6,15 @@ class MockRedis
6
6
  include Assertions
7
7
  include UtilityMethods
8
8
 
9
- def hdel(key, field)
9
+ def hdel(key, *fields)
10
10
  with_hash_at(key) do |hash|
11
- if field.is_a?(Array)
11
+ if fields.is_a?(Array)
12
12
  orig_size = hash.size
13
- fields = field.map(&:to_s)
13
+ fields = fields.map(&:to_s)
14
14
  hash.delete_if { |k, _v| fields.include?(k) }
15
15
  orig_size - hash.size
16
16
  else
17
- hash.delete(field.to_s) ? 1 : 0
17
+ hash.delete(fields[0].to_s) ? 1 : 0
18
18
  end
19
19
  end
20
20
  end
@@ -1,13 +1,5 @@
1
1
  class MockRedis
2
2
  class IndifferentHash < Hash
3
- def [](key)
4
- super(key.to_s)
5
- end
6
-
7
- def []=(key, value)
8
- super(key.to_s, value)
9
- end
10
-
11
3
  def has_key?(key)
12
4
  super(key.to_s)
13
5
  end
@@ -146,13 +146,13 @@ class MockRedis
146
146
 
147
147
  def ltrim(key, start, stop)
148
148
  with_list_at(key) do |list|
149
- list.replace(list[[start.to_i, -list.length].max..stop.to_i] || []) if list
149
+ list&.replace(list[[start.to_i, -list.length].max..stop.to_i] || [])
150
150
  'OK'
151
151
  end
152
152
  end
153
153
 
154
154
  def rpop(key)
155
- with_list_at(key) { |list| list.pop if list }
155
+ with_list_at(key) { |list| list&.pop }
156
156
  end
157
157
 
158
158
  def rpoplpush(source, destination)
@@ -9,7 +9,7 @@ class MockRedis
9
9
  def initialize(db)
10
10
  @db = db
11
11
  @pipelined_futures = []
12
- @in_pipeline = false
12
+ @nesting_level = 0
13
13
  end
14
14
 
15
15
  def initialize_copy(source)
@@ -19,7 +19,7 @@ class MockRedis
19
19
  end
20
20
 
21
21
  def method_missing(method, *args, &block)
22
- if @in_pipeline
22
+ if in_pipeline?
23
23
  future = MockRedis::Future.new([method, *args], block)
24
24
  @pipelined_futures << future
25
25
  future
@@ -29,9 +29,17 @@ class MockRedis
29
29
  end
30
30
 
31
31
  def pipelined(_options = {})
32
- @in_pipeline = true
33
- yield self
34
- @in_pipeline = false
32
+ begin
33
+ @nesting_level += 1
34
+ yield self
35
+ ensure
36
+ @nesting_level -= 1
37
+ end
38
+
39
+ if in_pipeline?
40
+ return
41
+ end
42
+
35
43
  responses = @pipelined_futures.flat_map do |future|
36
44
  begin
37
45
  result = if future.block
@@ -40,7 +48,12 @@ class MockRedis
40
48
  send(*future.command)
41
49
  end
42
50
  future.store_result(result)
43
- result
51
+
52
+ if future.block
53
+ result
54
+ else
55
+ [result]
56
+ end
44
57
  rescue StandardError => e
45
58
  e
46
59
  end
@@ -48,5 +61,11 @@ class MockRedis
48
61
  @pipelined_futures = []
49
62
  responses
50
63
  end
64
+
65
+ private
66
+
67
+ def in_pipeline?
68
+ @nesting_level > 0
69
+ end
51
70
  end
52
71
  end
@@ -81,11 +81,22 @@ class MockRedis
81
81
  end
82
82
  end
83
83
 
84
- def spop(key)
84
+ def spop(key, count = nil)
85
85
  with_set_at(key) do |set|
86
- member = set.first
87
- set.delete(member)
88
- member
86
+ if count.nil?
87
+ member = set.first
88
+ set.delete(member)
89
+ member
90
+ else
91
+ members = []
92
+ count.times do
93
+ member = set.first
94
+ break if member.nil?
95
+ set.delete(member)
96
+ members << member
97
+ end
98
+ members
99
+ end
89
100
  end
90
101
  end
91
102
 
@@ -0,0 +1,62 @@
1
+ require 'forwardable'
2
+ require 'set'
3
+ require 'date'
4
+ require 'mock_redis/stream/id'
5
+
6
+ class MockRedis
7
+ class Stream
8
+ include Enumerable
9
+ extend Forwardable
10
+
11
+ attr_accessor :members
12
+
13
+ def_delegators :members, :empty?
14
+
15
+ def initialize
16
+ @members = Set.new
17
+ @last_id = nil
18
+ end
19
+
20
+ def last_id
21
+ @last_id.to_s
22
+ end
23
+
24
+ def add(id, values)
25
+ @last_id = MockRedis::Stream::Id.new(id, min: @last_id)
26
+ members.add [@last_id, Hash[values.map { |k, v| [k.to_s, v.to_s] }]]
27
+ @last_id.to_s
28
+ end
29
+
30
+ def trim(count)
31
+ deleted = @members.size - count
32
+ @members = @members.to_a[-count..-1].to_set
33
+ deleted
34
+ end
35
+
36
+ def range(start, finish, reversed, *opts_in)
37
+ opts = options opts_in, ['count']
38
+ start_id = MockRedis::Stream::Id.new(start)
39
+ finish_id = MockRedis::Stream::Id.new(finish, sequence: Float::INFINITY)
40
+ items = members
41
+ .select { |m| (start_id <= m[0]) && (finish_id >= m[0]) }
42
+ .map { |m| [m[0].to_s, m[1]] }
43
+ items.reverse! if reversed
44
+ return items.first(opts['count'].to_i) if opts.key?('count')
45
+ items
46
+ end
47
+
48
+ def each
49
+ members.each { |m| yield m }
50
+ end
51
+
52
+ private
53
+
54
+ def options(opts_in, permitted)
55
+ opts_out = {}
56
+ raise Redis::CommandError, 'ERR syntax error' unless (opts_in.length % 2).zero?
57
+ opts_in.each_slice(2).map { |pair| opts_out[pair[0].downcase] = pair[1] }
58
+ raise Redis::CommandError, 'ERR syntax error' unless (opts_out.keys - permitted).empty?
59
+ opts_out
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,53 @@
1
+ class MockRedis
2
+ class Stream
3
+ class Id
4
+ include Comparable
5
+
6
+ attr_accessor :timestamp, :sequence
7
+
8
+ def initialize(id, min: nil, sequence: 0)
9
+ case id
10
+ when '*'
11
+ @timestamp = (Time.now.to_f * 1000).to_i
12
+ @sequence = 0
13
+ if self <= min
14
+ @timestamp = min.timestamp
15
+ @sequence = min.sequence + 1
16
+ end
17
+ when '-'
18
+ @timestamp = @sequence = 0
19
+ when '+'
20
+ @timestamp = @sequence = Float::INFINITY
21
+ else
22
+ if id.is_a? String
23
+ (_, @timestamp, @sequence) = id.match(/^(\d+)-?(\d+)?$/)
24
+ .to_a
25
+ if @timestamp.nil?
26
+ raise Redis::CommandError,
27
+ 'ERR Invalid stream ID specified as stream command argument'
28
+ end
29
+ @timestamp = @timestamp.to_i
30
+ else
31
+ @timestamp = id
32
+ end
33
+ @sequence = @sequence.nil? ? sequence : @sequence.to_i
34
+ if (@timestamp == 0 && @sequence == 0) || self <= min
35
+ raise Redis::CommandError,
36
+ 'ERR The ID specified in XADD is equal or smaller than ' \
37
+ 'the target stream top item'
38
+ end
39
+ end
40
+ end
41
+
42
+ def to_s
43
+ "#{@timestamp}-#{@sequence}"
44
+ end
45
+
46
+ def <=>(other)
47
+ return 1 if other.nil?
48
+ return @sequence <=> other.sequence if @timestamp == other.timestamp
49
+ @timestamp <=> other.timestamp
50
+ end
51
+ end
52
+ end
53
+ end