guilleiguaran / fakeredis

In-memory driver for redis-rb, useful for development and test environments
http://guilleiguaran.github.com/fakeredis
MIT License
560 stars 160 forks source link

script command for Redis-cli v2.6.17 #94

Open rbrenes opened 10 years ago

rbrenes commented 10 years ago

ERR unknown command 'script' (Redis::CommandError) tryint to do a Redis.script( :load, @script_file_content )

rbrenes commented 10 years ago

evalsha command missing too, for redis-cli v2.6.17

bsubramaniam commented 10 years ago

Is anyone working on this ? @rbrenes What is the workaround you are using ?

hurricane766 commented 8 years ago

Bump. We're trying to use fakeredis in testing with the Kue package. fakeredis crashes and says the script command is not implemented and to tell you if we want it :+1:

carsonreinke commented 8 years ago

Without adding a bunch (including Lua) into this gem, I started forking Redis when starting any tests and terminating on Kernel.at_exit.

hurricane766 commented 8 years ago

Ya, for now we've done something similar... just defeats the purpose of fakeredis, and the error said we should tell guilleiguaran if we want it.

carsonreinke commented 8 years ago

One alternative would be to use camcorder to mock those missing Redis methods.

hobodave commented 8 years ago

I've been working around the absence of the script, eval, and evalsha commands by just implementing the specific functionality I need for testing. Remember, the primary reason for using LUA scripts with Redis is for the atomicity -- this is usually not needed in testing.

For example, we use the Redlock gem which currently makes use of script and evalsha (earlier versions used eval only).

# spec/support/redlock_helper.rb
# Monkey patching script and evalsha into Fakeredis for Redlock usage in M3LocationSyncWorkerSpec
class Redis
  module Connection
    class Memory
      UNLOCK_SCRIPT_SHA = 'abcd'
      LOCK_SCRIPT_SHA = 'bcde'
      EXTEND_LIFE_SCRIPT_SHA = 'cdef'

      def script(*args)
        case args[1]
        when Redlock::Client::RedisInstance::UNLOCK_SCRIPT
          return UNLOCK_SCRIPT_SHA
        when Redlock::Client::RedisInstance::LOCK_SCRIPT
          return LOCK_SCRIPT_SHA
        when Redlock::Client::RedisInstance::EXTEND_LIFE_SCRIPT
          return EXTEND_LIFE_SCRIPT_SHA
        else
          raise Redis::CommandError, "ERR unknown command 'script'"
        end
      end

      def evalsha(*args)
        case args[0]
        when UNLOCK_SCRIPT_SHA
          if get(args[2]) == args[3]
            del(args[2])
          else
            return 0
          end
        when LOCK_SCRIPT_SHA
          if !exists(args[2]) || get(args[2]) == args[3]
            set(args[2], args[3], 'PX', args[4])
          end
        when EXTEND_LIFE_SCRIPT_SHA
          if get(args[2]) == args[3]
            expire(args[2], args[4])
            return 0
          else
            return 1
          end
        else
          raise Redis::CommandError, "ERR unknown command 'evalsha'"
        end
      end
    end
  end
end

The script method above assumes you're calling :load (which is all Redlock does) and then just returns a hardcoded SHA for each script.

The evalsha method then implements the LUA found in the script in Ruby.

Both methods preserve the behavior of raising when an unknown script is eval'd or loaded.

carsonreinke commented 8 years ago

This is making the assumption that Redlock will not calculate the SHA independently of Redis.

polmuz commented 1 year ago

I had to update the stubs to support redlock 1.3 and fakeredis 0.9

# Based on https://github.com/guilleiguaran/fakeredis/issues/94
# Updated for redlock 1.3
# Monkey patching evalsha into Fakeredis for Redlock usage
class Redis
  module Connection
    class Memory
      def evalsha(*args)
        script_sha = args[0]
        mystery_arg = args[1]
        resource = args[2]
        argv = args[3..]
        case script_sha
        when Redlock::Scripts::UNLOCK_SCRIPT_SHA
          # Original Redlock unlock Lua script to be translated to Fakeredis
          # if redis.call("get",KEYS[1]) == ARGV[1] then
          #   return redis.call("del",KEYS[1])
          # else
          #   return 0
          # end
          if get(resource) == argv[0]
            del(resource)
          else
            return 0
          end
        when Redlock::Scripts::LOCK_SCRIPT_SHA
          # Original Redlock lock Lua script to be translated to Fakeredis
          # if (redis.call("exists", KEYS[1]) == 0 and ARGV[3] == "yes") or redis.call("get", KEYS[1]) == ARGV[1] then
          #   return redis.call("set", KEYS[1], ARGV[1], "PX", ARGV[2])
          # end
          if !exists?(resource) || get(resource) == argv[0]
            set(resource, argv[0], 'PX', argv[1])
          end
        else
          raise Redis::CommandError, "ERR unknown command 'evalsha' for #{script_sha}"
        end
      end
    end
  end
end
timherby commented 8 months ago

Thanks @polmuz . This is exactly what I needed. This allowed me to test without mocking Redlock itself, which is great!

BTW, Redlock 1.3.2 needed Redlock::Scripts::PTTL_SCRIPT_SHA as well, so I've added it below:

# Based on https://github.com/guilleiguaran/fakeredis/issues/94
# Updated for redlock 1.3
# Monkey patching evalsha into Fakeredis for Redlock usage
class Redis
  module Connection
    class Memory
      def evalsha(*args)
        script_sha = args[0]
        mystery_arg = args[1]
        resource = args[2]
        argv = args[3..]
        case script_sha
        when Redlock::Scripts::UNLOCK_SCRIPT_SHA
          # Original Redlock unlock Lua script to be translated to Fakeredis
          # if redis.call("get",KEYS[1]) == ARGV[1] then
          #   return redis.call("del",KEYS[1])
          # else
          #   return 0
          # end
          if get(resource) == argv[0]
            del(resource)
          else
            0
          end
        when Redlock::Scripts::LOCK_SCRIPT_SHA
          # Original Redlock lock Lua script to be translated to Fakeredis
          # if (redis.call("exists", KEYS[1]) == 0 and ARGV[3] == "yes") or redis.call("get", KEYS[1]) == ARGV[1] then
          #   return redis.call("set", KEYS[1], ARGV[1], "PX", ARGV[2])
          # end
          if !exists?(resource) || get(resource) == argv[0]
            set(resource, argv[0], 'PX', argv[1])
          end
        when Redlock::Scripts::PTTL_SCRIPT_SHA
          # Original Redlock lock Lua script to be translated to Fakeredis
          # return { redis.call("get", KEYS[1]), redis.call("pttl", KEYS[1]) }
          value = get(resource)
          ttl = pttl(resource)
          [value, ttl]
        else
          raise Redis::CommandError, "ERR unknown command 'evalsha' for #{script_sha}"
        end
      end
    end
  end
end