Class: PseudoCleaner::RedisCleaner

Inherits:
Object
  • Object
show all
Defined in:
lib/pseudo_cleaner/redis_cleaner.rb

Overview

I’m not a huge fan of sleeps. In the non-rails world, I used to be able to do a sleep(0) to signal the system to check if somebody else needed to do some work. Testing with Rails, I find I have to actually sleep, so I do a very short time like 0.01.

Direct Known Subclasses

RedisBasedRedisCleaner

Constant Summary collapse

COMMANDS =

copied from Redis::Namespace

{
    "append"           => [:first],
    "auth"             => [],
    "bgrewriteaof"     => [],
    "bgsave"           => [],
    "bitcount"         => [:first],
    "bitop"            => [:exclude_first],
    "blpop"            => [:exclude_last, :first],
    "brpop"            => [:exclude_last, :first],
    "brpoplpush"       => [:exclude_last],
    "config"           => [],
    "dbsize"           => [],
    "debug"            => [:exclude_first],
    "decr"             => [:first],
    "decrby"           => [:first],
    "del"              => [:all],
    "discard"          => [],
    "disconnect!"      => [],
    "dump"             => [:first],
    "echo"             => [],
    "exists"           => [:first],
    "expire"           => [:first],
    "expireat"         => [:first],
    "eval"             => [:eval_style],
    "evalsha"          => [:eval_style],
    "exec"             => [],
    "flushall"         => [],
    "flushdb"          => [],
    "get"              => [:first],
    "getbit"           => [:first],
    "getrange"         => [:first],
    "getset"           => [:first],
    "hset"             => [:first],
    "hsetnx"           => [:first],
    "hget"             => [:first],
    "hincrby"          => [:first],
    "hincrbyfloat"     => [:first],
    "hmget"            => [:first],
    "hmset"            => [:first],
    "hdel"             => [:first],
    "hexists"          => [:first],
    "hlen"             => [:first],
    "hkeys"            => [:first],
    "hscan"            => [:first],
    "hscan_each"       => [:first],
    "hvals"            => [:first],
    "hgetall"          => [:first],
    "incr"             => [:first],
    "incrby"           => [:first],
    "incrbyfloat"      => [:first],
    "info"             => [],
    "keys"             => [:first, :all],
    "lastsave"         => [],
    "lindex"           => [:first],
    "linsert"          => [:first],
    "llen"             => [:first],
    "lpop"             => [:first],
    "lpush"            => [:first],
    "lpushx"           => [:first],
    "lrange"           => [:first],
    "lrem"             => [:first],
    "lset"             => [:first],
    "ltrim"            => [:first],
    "mapped_hmset"     => [:first],
    "mapped_hmget"     => [:first],
    "mapped_mget"      => [:all, :all],
    "mapped_mset"      => [:all],
    "mapped_msetnx"    => [:all],
    "mget"             => [:all],
    "monitor"          => [:monitor],
    "move"             => [:first],
    "multi"            => [],
    "mset"             => [:alternate],
    "msetnx"           => [:alternate],
    "object"           => [:exclude_first],
    "persist"          => [:first],
    "pexpire"          => [:first],
    "pexpireat"        => [:first],
    "pfadd"            => [:first],
    "pfcount"          => [:all],
    "pfmerge"          => [:all],
    "ping"             => [],
    "psetex"           => [:first],
    "psubscribe"       => [:all],
    "pttl"             => [:first],
    "publish"          => [:first],
    "punsubscribe"     => [:all],
    "quit"             => [],
    "randomkey"        => [],
    "rename"           => [:all],
    "renamenx"         => [:all],
    "restore"          => [:first],
    "rpop"             => [:first],
    "rpoplpush"        => [:all],
    "rpush"            => [:first],
    "rpushx"           => [:first],
    "sadd"             => [:first],
    "save"             => [],
    "scard"            => [:first],
    "scan"             => [:scan_style, :second],
    "scan_each"        => [:scan_style, :all],
    "script"           => [],
    "sdiff"            => [:all],
    "sdiffstore"       => [:all],
    "select"           => [],
    "set"              => [:first],
    "setbit"           => [:first],
    "setex"            => [:first],
    "setnx"            => [:first],
    "setrange"         => [:first],
    "shutdown"         => [],
    "sinter"           => [:all],
    "sinterstore"      => [:all],
    "sismember"        => [:first],
    "slaveof"          => [],
    "smembers"         => [:first],
    "smove"            => [:exclude_last],
    "sort"             => [:sort],
    "spop"             => [:first],
    "srandmember"      => [:first],
    "srem"             => [:first],
    "sscan"            => [:first],
    "sscan_each"       => [:first],
    "strlen"           => [:first],
    "subscribe"        => [:all],
    "sunion"           => [:all],
    "sunionstore"      => [:all],
    "ttl"              => [:first],
    "type"             => [:first],
    "unsubscribe"      => [:all],
    "unwatch"          => [:all],
    "watch"            => [:all],
    "zadd"             => [:first],
    "zcard"            => [:first],
    "zcount"           => [:first],
    "zincrby"          => [:first],
    "zinterstore"      => [:exclude_options],
    "zrange"           => [:first],
    "zrangebylex"      => [:first],
    "zrangebyscore"    => [:first],
    "zrank"            => [:first],
    "zrem"             => [:first],
    "zremrangebyrank"  => [:first],
    "zremrangebyscore" => [:first],
    "zremrangebylex"   => [:first],
    "zrevrange"        => [:first],
    "zrevrangebyscore" => [:first],
    "zrevrangebylex"   => [:first],
    "zrevrank"         => [:first],
    "zscan"            => [:first],
    "zscan_each"       => [:first],
    "zscore"           => [:first],
    "zunionstore"      => [:exclude_options],
    "[]"               => [:first],
    "[]="              => [:first]
}
FLUSH_COMMANDS =
[
    "flushall",
    "flushdb"
]
NUM_CHANGED_COMMANDS =
[
    "sadd",
    "zadd",
    "srem",
    "zrem",
    "zremrangebyrank",
    "zremrangebyscore",
    "zremrangebylex",
    "hsetnx",
    "hdel",
    "linsert",
    "lpushx",
    "rpushx",
    "lrem",
    "mapped_msetnx",
    "msetnx",
    "move",
    "persist",
    "renamenx",
    "sdiffstore",
    "setnx",
    "sinterstore",
    "smove",
    "sunionstore",
    "zinterstore",
    "zunionstore",
]
NIL_FAIL_COMMANDS =
[
    "lpop",
    "rpop",
    "rpoplpush",
    "spop",
]
POP_COMMANDS =
[
    "blpop",
    "brpop",
]
WRITE_COMMANDS =
[
    "append",
    "bitop",
    "brpoplpush",
    "decr",
    "decrby",
    "del",
    "expire",
    "expireat",
    "getset",
    "hset",
    "hincrby",
    "hincrbyfloat",
    "hmset",
    "incr",
    "incrby",
    "incrbyfloat",
    "lpush",
    "lset",
    "ltrim",
    "mapped_hmset",
    "mapped_mset",
    "mset",
    "pexpire",
    "pexpireat",
    "psetex",
    "rename",
    "restore",
    "rpush",
    "set",
    "setbit",
    "setex",
    "setrange",
    "sort",
    "zincrby",
    "[]=",
]
READ_COMMANDS =
[
    "bitcount",
    "dump",
    "exists",
    "get",
    "getbit",
    "getrange",
    "hget",
    "hmget",
    "hexists",
    "hlen",
    "hkeys",
    "hscan",
    "hscan_each",
    "hvals",
    "hgetall",
    "lindex",
    "llen",
    "lrange",
    "mapped_hmget",
    "mapped_mget",
    "mget",
    "scard",
    "scan",
    "scan_each",
    "sdiff",
    "sismember",
    "smembers",
    "srandmember",
    "sscan",
    "sscan_each",
    "strlen",
    "sunion",
    "type",
    "zcard",
    "zcount",
    "zlexcount",
    "zrange",
    "zrangebylex",
    "zrangebyscore",
    "zrank",
    "zrevrange",
    "zrevrangebylex",
    "zrevrangebyscore",
    "zrevrank",
    "zscan",
    "zscan_each",
    "zscore",
    "[]",
]
ALL_COMMANDS =
READ_COMMANDS +
FLUSH_COMMANDS +
NUM_CHANGED_COMMANDS +
NIL_FAIL_COMMANDS +
POP_COMMANDS +
WRITE_COMMANDS
OVERRIDE_COMMANDS =
{
    "sdiffstore"  => [:first],
    "sinterstore" => [:first],
    "zinterstore" => [:first],
    "sunionstore" => [:first],
    "zunionstore" => [:first],
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(start_method, end_method, table, options) ⇒ RedisCleaner

Returns a new instance of RedisCleaner.



351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 351

def initialize(start_method, end_method, table, options)
  @redis      = table
  @redis_name = nil

  clear_set :@initial_keys
  clear_set :@suite_altered_keys
  clear_set :@updated_keys
  clear_list_array :@read_keys
  clear_list_array :@multi_commands
  set_value_bool :@in_multi, false
  set_value_bool :@in_redis_cleanup, false
  set_value_bool :@suspend_tracking, false

  unless PseudoCleaner::MasterCleaner::VALID_START_METHODS.include?(start_method)
    raise "You must specify a valid start function from: #{PseudoCleaner::MasterCleaner::VALID_START_METHODS}."
  end
  unless PseudoCleaner::MasterCleaner::VALID_END_METHODS.include?(end_method)
    raise "You must specify a valid end function from: #{PseudoCleaner::MasterCleaner::VALID_END_METHODS}."
  end

  @options = options

  @options[:table_start_method] ||= start_method
  @options[:table_end_method]   ||= end_method
  @options[:output_diagnostics] ||= PseudoCleaner::Configuration.current_instance.output_diagnostics ||
      PseudoCleaner::Configuration.current_instance.post_transaction_analysis
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(command, *args, &block) ⇒ Object



392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 392

def method_missing(command, *args, &block)
  normalized_command = command.to_s.downcase

  if redis.respond_to?(normalized_command)
    if (normalized_command == "pipelined" ||
        (normalized_command == "multi" && block)) &&
        !get_value_bool(:@suspend_tracking)
      set_value_bool :@in_multi, true
      normalized_command = "exec"
    end

    initial_keys = nil
    if FLUSH_COMMANDS.include?(normalized_command)
      initial_keys = get_set(:@initial_keys)
    end

    response = redis.send(command, *args, &block)

    if FLUSH_COMMANDS.include?(normalized_command)
      add_set_values :@updated_keys, *initial_keys.to_a
    end

    if get_value_bool(:@in_multi) && !(["exec", "discard"].include?(normalized_command))
      append_list_value_array :@multi_commands, [normalized_command, *args]
    else
      process_command(response, normalized_command, *args)
    end

    response
  else
    super
  end
end

Instance Attribute Details

#optionsObject

Returns the value of attribute options.



349
350
351
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 349

def options
  @options
end

Instance Method Details

#<=>(right_object) ⇒ Object



582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 582

def <=>(right_object)
  if (right_object.is_a?(PseudoCleaner::RedisCleaner))
    return 0
  elsif (right_object.is_a?(PseudoCleaner::TableCleaner))
    return 1
  else
    if right_object.respond_to?(:<=>)
      comparison = (right_object <=> self)
      if comparison
        return -1 * comparison
      end
    end
  end

  return 1
end

#add_set_value(value_name, value) ⇒ Object



1019
1020
1021
1022
1023
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 1019

def add_set_value(value_name, value)
  set = get_set(value_name)

  set << value
end

#add_set_values(value_name, *values) ⇒ Object



1013
1014
1015
1016
1017
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 1013

def add_set_values(value_name, *values)
  set = get_set(value_name)

  set.merge(values)
end

#append_list_value_array(value_name, array_value) ⇒ Object



988
989
990
991
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 988

def append_list_value_array(value_name, array_value)
  array = instance_variable_get(value_name)
  array << array_value
end

#clear_list_array(value_name) ⇒ Object



1001
1002
1003
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 1001

def clear_list_array(value_name)
  instance_variable_set(value_name, [])
end

#clear_set(value_name, keys = nil) ⇒ Object



1005
1006
1007
1008
1009
1010
1011
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 1005

def clear_set(value_name, keys = nil)
  if keys
    instance_variable_set(value_name, SortedSet.new(keys))
  else
    instance_variable_set(value_name, SortedSet.new)
  end
end

#extract_key(arg) ⇒ Object



506
507
508
509
510
511
512
513
514
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 506

def extract_key(arg)
  if arg.is_a?(Array)
    arg
  elsif arg.is_a?(Hash)
    arg.keys
  else
    [arg]
  end
end

#extract_keys(command, *args) ⇒ Object



516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 516

def extract_keys(command, *args)
  handling     = OVERRIDE_COMMANDS[command.to_s.downcase] || PseudoCleaner::RedisCleaner::COMMANDS[command.to_s.downcase]
  message_keys = []

  (before, after) = handling

  case before
    when :first
      if args[0]
        extract_key(args[0]).each do |key|
          message_keys << key
        end
      end

    when :all
      args.each do |arg|
        extract_key(arg).each do |key|
          message_keys << key
        end
      end

    when :exclude_first
      args.each do |arg|
        extract_key(arg).each do |key|
          message_keys << key
        end
      end
      message_keys.shift

    when :exclude_last
      args.each do |arg|
        extract_key(arg).each do |key|
          message_keys << key
        end
      end
      message_keys.pop unless message_keys.length == 1

    when :exclude_options
      args.each do |arg|
        message_keys << arg unless arg.is_a?(Hash)
      end

    when :alternate
      args.each_with_index do |arg, i|
        if i.even?
          extract_key(arg).each do |key|
            message_keys << key
          end
        end
      end

    when :sort
      if args[-1].is_a?(Hash)
        if args[-1][:store] || args[-1]["store"]
          message_keys << (args[-1][:store] || args[-1]["store"])
        end
      end

    # when :eval_style
    #
    # when :scan_style
  end

  message_keys
end

#get_list_array(value_name) ⇒ Object



997
998
999
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 997

def get_list_array(value_name)
  instance_variable_get(value_name)
end

#get_list_length(value_name) ⇒ Object



993
994
995
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 993

def get_list_length(value_name)
  instance_variable_get(value_name).length
end

#get_set(value_name) ⇒ Object



1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 1037

def get_set(value_name)
  set = instance_variable_get(value_name)

  unless set
    set = SortedSet.new
    instance_variable_set(value_name, set)
  end

  set
end

#get_value_bool(value_name) ⇒ Object



984
985
986
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 984

def get_value_bool(value_name)
  instance_variable_get(value_name)
end

#ignore_key(key) ⇒ Object



708
709
710
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 708

def ignore_key(key)
  ignore_regexes.detect { |ignore_regex| key =~ ignore_regex }
end

#ignore_regexesObject



704
705
706
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 704

def ignore_regexes
  []
end

#peek_valuesObject



739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 739

def peek_values
  time = Benchmark.measure do
    puts "  RedisCleaner(#{redis_name})" if PseudoCleaner::Configuration.instance.benchmark

    synchronize_test_values do |updated_values, read_values|
      if (updated_values && !updated_values.empty?) || (read_values && !read_values.empty?)
        output_values = false

        updated_values = updated_values.select do |value|
          value, read_value = split_read_values(value)
          !ignore_key(value)
        end
        read_values    = read_values.select do |value|
          value, read_value = split_read_values(value)
          !ignore_key(value)
        end

        if PseudoCleaner::MasterCleaner.report_table
          Cornucopia::Util::ReportTable.new(nested_table:         PseudoCleaner::MasterCleaner.report_table,
                                            nested_table_label:   redis_name,
                                            suppress_blank_table: true) do |report_table|
            updated_values.each_with_index do |updated_value, index|
              updated_value, read_value = split_read_values(updated_value)
              unless ignore_key(updated_value)
                output_values = true
                report_table.write_stats index.to_s, report_record(updated_value)
                report_table.write_stats "", read_value if read_value
              end
            end
          end

          if track_reads
            Cornucopia::Util::ReportTable.new(nested_table:         PseudoCleaner::MasterCleaner.report_table,
                                              nested_table_label:   "#{redis_name} - reads",
                                              suppress_blank_table: true) do |report_table|
              read_values.each_with_index do |updated_value, index|
                updated_value, read_value = split_read_values(updated_value)
                unless ignore_key(updated_value)
                  output_values = true
                  report_table.write_stats index.to_s, report_record(updated_value)
                  report_table.write_stats "", read_value if read_value
                end
              end
            end
          end
        else
          PseudoCleaner::Logger.write("  #{redis_name}")

          updated_values.each_with_index do |updated_value, index|
            updated_value, read_value = split_read_values(updated_value)
            unless ignore_key(updated_value)
              output_values = true
              PseudoCleaner::Logger.write("    #{index}: #{report_record(updated_value)}")
              PseudoCleaner::Logger.write("       #{read_value}") if read_value
            end
          end

          if track_reads
            PseudoCleaner::Logger.write("  #{redis_name} - reads")

            read_values.each_with_index do |updated_value, index|
              updated_value, read_value = split_read_values(updated_value)
              unless ignore_key(updated_value)
                output_values = true
                PseudoCleaner::Logger.write("    #{index}: #{report_record(updated_value)}")
                PseudoCleaner::Logger.write("       #{read_value}") if read_value
              end
            end
          end
        end

        PseudoCleaner::MasterCleaner.report_error if output_values
      end
    end
  end

  puts "  RedisCleaner(#{redis_name}) time: #{time}" if PseudoCleaner::Configuration.instance.benchmark
end

#process_command(response, *args) ⇒ Object



426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 426

def process_command(response, *args)
  unless get_value_bool(:@in_redis_cleanup) || get_value_bool(:@suspend_tracking)
    if "multi" == args[0]
      set_value_bool :@in_multi, true
      clear_list_array :@multi_commands
    elsif ["exec", "pipelined"].include?(args[0])
      begin
        if (!response && get_list_length(:@multi_commands) > 0) ||
            (response && response.length != get_list_length(:@multi_commands))
          puts "exec response does not match sent commands.\n  response: #{response}\n  commands: #{get_list_array(:@multi_commands)}"

          # make the response length match the commands length.
          # so far the only time this has happened was when a multi returned nil which SHOULD indicate a failure
          #
          # I am assuming that the multi failed in this case, but even if so, it is safest for tracking purposes
          # to assume that redis DID change and record it as such.  Even if I am wrong, for the cleaner, it
          # doesn't matter, and there is no harm.
          response ||= []
          get_list_array(:@multi_commands).each_with_index do |command, index|
            if response.length < index
              response << true
            end
          end
        end

        get_list_array(:@multi_commands).each_with_index do |command, index|
          process_command(response[index], *command)
        end
      ensure
        set_value_bool :@in_multi, false
        clear_list_array :@multi_commands
      end
    elsif "discard" == args[0]
      set_value_bool :@in_multi, false
      clear_list_array :@multi_commands
    elsif WRITE_COMMANDS.include?(args[0])
      add_set_values :@updated_keys, *extract_keys(*args)
    elsif NUM_CHANGED_COMMANDS.include?(args[0])
      update_key = true
      if [true, false].include?(response)
        update_key = response
      else
        update_key = response > 0 rescue true
      end

      if update_key
        add_set_values :@updated_keys, *extract_keys(*args)
      end
    elsif POP_COMMANDS.include?(args[0])
      if response
        add_set_value :@updated_keys, response[0]
      end
    elsif NIL_FAIL_COMMANDS.include?(args[0])
      if response
        add_set_values :@updated_keys, *extract_keys(*args)
      end
    elsif track_reads && READ_COMMANDS.include?(args[0])
      keys = extract_keys(*args)
      append_list_value_array(:@read_keys,
                              [
                                  keys.length,
                                  *keys,
                                  response.to_s.encode('UTF-8', invalid: :replace, undef: :replace, replace: '')
                              ]
      )
    end
  end
end

#redisObject



599
600
601
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 599

def redis
  @redis ||= Redis.current
end

#redis_nameObject



712
713
714
715
716
717
718
719
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 712

def redis_name
  unless @redis_name
    redis_options = redis.client.options.with_indifferent_access
    @redis_name   = "#{redis_options[:host]}:#{redis_options[:port]}/#{redis_options[:db]}"
  end

  @redis_name
end

#remove_set_value(value_name, value) ⇒ Object



1025
1026
1027
1028
1029
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 1025

def remove_set_value(value_name, value)
  set = get_set(value_name)

  set.delete value
end

#report_dirty_values(message, test_values) ⇒ Object



944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 944

def report_dirty_values message, test_values
  test_values = test_values.select { |value| !ignore_key(split_read_values(value)[0]) }

  if test_values && !test_values.empty?
    output_values = false

    if PseudoCleaner::MasterCleaner.report_table
      Cornucopia::Util::ReportTable.new(nested_table:         PseudoCleaner::MasterCleaner.report_table,
                                        nested_table_label:   redis_name,
                                        suppress_blank_table: true) do |report_table|
        report_table.write_stats "action", message
        test_values.each_with_index do |key_name, index|
          key_name, read_value = split_read_values(key_name)
          unless ignore_key(key_name)
            output_values = true
            report_table.write_stats index, report_record(key_name)
            report_table.write_stats("", read_value) if read_value
          end
        end
      end
    else
      test_values.each do |key_name|
        key_name, read_value = split_read_values(key_name)
        unless ignore_key(key_name)
          PseudoCleaner::Logger.write("********* RedisCleaner - #{message}".red.on_light_white) unless output_values
          output_values = true
          PseudoCleaner::Logger.write("  #{key_name}: #{report_record(key_name)}".red.on_light_white)
          PseudoCleaner::Logger.write("     #{read_value}".red.on_light_white) if read_value
        end
      end
    end

    PseudoCleaner::MasterCleaner.report_error if output_values
  end
end

#report_end_of_suite_state(report_reason) ⇒ Object



818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 818

def report_end_of_suite_state report_reason
  current_keys = SortedSet.new(redis.keys)

  initial_key_set = get_set(:@initial_keys)

  deleted_keys = initial_key_set - current_keys
  new_keys     = current_keys - initial_key_set

  # filter out values we inserted that will go away on their own.
  new_keys     = new_keys.select { |key| (key =~ /redis_cleaner::synchronization_(?:end_)?key_[0-9]+_[0-9]+/).nil? }

  report_dirty_values "new values as of #{report_reason}", new_keys
  report_dirty_values "values deleted before #{report_reason}", deleted_keys
  report_dirty_values "initial values changed during suite run", get_set(:@suite_altered_keys)

  clear_set :@suite_altered_keys

  new_keys
end

#report_record(key_name) ⇒ Object



906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 906

def report_record(key_name)
  key_hash = { key: key_name, type: redis.type(key_name), ttl: redis.ttl(key_name) }
  case key_hash[:type]
    when "string"
      key_hash[:value] = redis.get(key_name)
    when "list"
      key_hash[:list] = { len: redis.llen(key_name), values: redis.lrange(key_name, 0, -1) }
    when "set"
      key_hash[:set] = { len: redis.scard(key_name), values: redis.smembers(key_name) }
    when "zset"
      sorted_set_values = redis.zrange(key_name, 0, -1).reduce({}) do |hash, set_value|
        hash[set_value] = redis.zscore(key_name, set_value)
        hash
      end

      key_hash[:sorted_set] = { len: redis.zcard(key_name), values: sorted_set_values }
    when "hash"
      key_hash[:hash] = { len: redis.hlen(key_name), values: redis.hgetall(key_name) }
  end

  if key_hash[:value].nil? &&
      key_hash[:list].nil? &&
      key_hash[:set].nil? &&
      key_hash[:sorted_set].nil? &&
      key_hash[:hash].nil?
    key_hash[:value] = "[[DELETED]]"
  end

  key_hash
end

#reset_suiteObject



692
693
694
695
696
697
698
699
700
701
702
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 692

def reset_suite
  time = Benchmark.measure do
    puts "  RedisCleaner(#{redis_name})" if PseudoCleaner::Configuration.instance.benchmark

    report_end_of_suite_state "reset suite"

    start_monitor
  end

  puts "  RedisCleaner(#{redis_name}) time: #{time}" if PseudoCleaner::Configuration.instance.benchmark
end

#respond_to?(command, include_private = false) ⇒ Boolean

emulate Ruby 1.9+ and keep respond_to_missing? logic together.

Returns:

  • (Boolean)


388
389
390
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 388

def respond_to?(command, include_private=false)
  super or respond_to_missing?(command, include_private)
end

#respond_to_missing?(command, include_all = false) ⇒ Boolean

Returns:

  • (Boolean)


495
496
497
498
499
500
501
502
503
504
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 495

def respond_to_missing?(command, include_all=false)
  return true if ALL_COMMANDS.include?(command.to_s.downcase)

  # blind passthrough is deprecated and will be removed in 2.0
  if redis.respond_to?(command, include_all)
    return true
  end

  defined?(super) && super
end

#review_rows(&block) ⇒ Object



721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 721

def review_rows(&block)
  time = Benchmark.measure do
    puts "  RedisCleaner(#{redis_name})" if PseudoCleaner::Configuration.instance.benchmark

    synchronize_test_values do |updated_values, read_values|
      if updated_values && !updated_values.empty?
        updated_values.each do |updated_value|
          unless ignore_key(updated_value)
            block.yield redis_name, report_record(updated_value)
          end
        end
      end
    end
  end

  puts "  RedisCleaner(#{redis_name}) time: #{time}" if PseudoCleaner::Configuration.instance.benchmark
end

#self_respond_to?Object



385
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 385

alias_method :self_respond_to?, :respond_to?

#set_includes?(value_name, value) ⇒ Boolean

Returns:

  • (Boolean)


1031
1032
1033
1034
1035
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 1031

def set_includes?(value_name, value)
  set = get_set(value_name)

  set.include?(value)
end

#set_value_bool(value_name, bool_value) ⇒ Object



980
981
982
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 980

def set_value_bool(value_name, bool_value)
  instance_variable_set(value_name, bool_value)
end

#split_read_values(key) ⇒ Object



937
938
939
940
941
942
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 937

def split_read_values(key)
  split = Array.wrap(key)
  split << nil if split.length < 2

  split
end

#start_monitorObject



881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 881

def start_monitor
  redis_keys = redis.keys
  clear_set :@initial_keys, redis_keys
  clear_set :@suite_altered_keys
  clear_set :@updated_keys
  clear_list_array :@read_keys
  clear_list_array :@multi_commands
  set_value_bool :@in_multi, false
  set_value_bool :@in_redis_cleanup, false
  set_value_bool :@suspend_tracking, false

  if @options[:output_diagnostics]
    if PseudoCleaner::MasterCleaner.report_table
      Cornucopia::Util::ReportTable.new(nested_table:         PseudoCleaner::MasterCleaner.report_table,
                                        nested_table_label:   redis_name,
                                        suppress_blank_table: true) do |report_table|
        report_table.write_stats "initial keys count", redis_keys.count
      end
    else
      PseudoCleaner::Logger.write("#{redis_name}")
      PseudoCleaner::Logger.write("    Initial keys count - #{redis_keys.count}")
    end
  end
end

#suite_end(test_strategy) ⇒ Object



678
679
680
681
682
683
684
685
686
687
688
689
690
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 678

def suite_end test_strategy
  time = Benchmark.measure do
    puts "  RedisCleaner(#{redis_name})" if PseudoCleaner::Configuration.instance.benchmark

    new_keys = report_end_of_suite_state "suite end"

    new_keys.each do |key_value|
      redis.del key_value
    end
  end

  puts "  RedisCleaner(#{redis_name}) time: #{time}" if PseudoCleaner::Configuration.instance.benchmark
end

#suite_start(test_strategy) ⇒ Object



603
604
605
606
607
608
609
610
611
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 603

def suite_start test_strategy
  time = Benchmark.measure do
    puts "  RedisCleaner(#{redis_name})" if PseudoCleaner::Configuration.instance.benchmark

    start_monitor
  end

  puts "  RedisCleaner(#{redis_name}) time: #{time}" if PseudoCleaner::Configuration.instance.benchmark
end

#suspend_tracking(&block) ⇒ Object



613
614
615
616
617
618
619
620
621
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 613

def suspend_tracking(&block)
  begin
    set_value_bool :@suspend_tracking, true

    block.yield
  ensure
    set_value_bool :@suspend_tracking, false
  end
end

#synchronize_test_values(&block) ⇒ Object



838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 838

def synchronize_test_values(&block)
  if get_value_bool(:@in_multi)
    # Ideally we should never get here, but if we do, assume everything was changed and keep moving...
    get_list_array(:@multi_commands).each do |args|
      if WRITE_COMMANDS.include?(args[0]) ||
          POP_COMMANDS.include?(args[0]) ||
          NIL_FAIL_COMMANDS.include?(args[0]) ||
          NUM_CHANGED_COMMANDS.include?(args[0])
        add_set_values :@updated_keys, *extract_keys(*args)
      elsif track_reads && READ_COMMANDS.include?(args[0])
        keys = *extract_keys(*args)
        append_list_value_array :@read_keys, [keys.length, *keys, response]
      end
    end

    set_value_bool(:@in_multi, false)
    clear_list_array(:@multi_commands)
  end

  updated_values = get_set(:@updated_keys).dup
  read_values    = get_list_array(:@read_keys)

  read_values = read_values.reduce([]) do |array, values|
    if values.length > values[0].to_i + 1
      read_value  = values[-1]
      values = values[1..-2].map { |value| [value, read_value] }
    else
      values = values[1..-1]
    end

    array.concat values

    array
  end
  set_value_bool :@in_redis_cleanup, true

  begin
    block.yield updated_values, read_values
  ensure
    set_value_bool :@in_redis_cleanup, false
  end
end

#test_end(test_strategy) ⇒ Object



645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 645

def test_end test_strategy
  time = Benchmark.measure do
    puts "  RedisCleaner(#{redis_name})" if PseudoCleaner::Configuration.instance.benchmark

    synchronize_test_values do |updated_values, read_values|
      if (updated_values && !updated_values.empty?) || (read_values && !read_values.empty?)
        report_keys = []

        if @options[:output_diagnostics]
          report_dirty_values "updated values", updated_values
          report_dirty_values "read values", read_values if track_reads
        end

        updated_values.each do |value|
          if set_includes?(:@initial_keys, value)
            report_keys << value
            add_set_value(:@suite_altered_keys, value) unless ignore_key(value)
          else
            redis.del(value)
          end
        end

        report_dirty_values "initial values altered by test", report_keys
      end
    end

    clear_set :@updated_keys
    clear_list_array :@read_keys
  end

  puts "  RedisCleaner(#{redis_name}) time: #{time}" if PseudoCleaner::Configuration.instance.benchmark
end

#test_start(test_strategy) ⇒ Object



623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 623

def test_start test_strategy
  time = Benchmark.measure do
    puts "  RedisCleaner(#{redis_name})" if PseudoCleaner::Configuration.instance.benchmark

    synchronize_test_values do |test_values, read_values|
      if (test_values && !test_values.empty?) || (read_values && !read_values.empty?)
        report_dirty_values "values altered before the test started", test_values
        report_dirty_values "values read before the test started", read_values if track_reads

        test_values.each do |value|
          redis.del value unless set_includes?(:@initial_keys, value)
        end
      end
    end

    clear_set :@updated_keys
    clear_list_array :@read_keys
  end

  puts "  RedisCleaner(#{redis_name}) time: #{time}" if PseudoCleaner::Configuration.instance.benchmark
end

#track_readsObject



1052
1053
1054
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 1052

def track_reads
  @track_reads ||= PseudoCleaner::Configuration.instance.redis_track_reads
end

#track_reads=(value) ⇒ Object



1048
1049
1050
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 1048

def track_reads=(value)
  @track_reads = value
end

#type(key) ⇒ Object

Ruby defines a now deprecated type method so we need to override it here since it will never hit method_missing



381
382
383
# File 'lib/pseudo_cleaner/redis_cleaner.rb', line 381

def type(key)
  redis.type(key)
end