mock_redis 0.19.0 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
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