Skip to content

Commit

Permalink
Make tests work with or without twemproxy
Browse files Browse the repository at this point in the history
This partially reverts some changes from https://github.com/3scale/apisonator/pull/370/files

`test/test_helpers/storage.rb` is now `test/test_helpers/twemproxy.rb`
  • Loading branch information
jlledom committed Jul 2, 2024
1 parent cacd53c commit 9071852
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 10 deletions.
11 changes: 11 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ def committed_at
require_relative '../test/test_helpers/sequences.rb'

RSpec.configure do |config|
config.before :suite do
require_relative '../test/test_helpers/configuration'
require_relative '../test/test_helpers/twemproxy'

TestHelpers::Storage::Mock.mock_storage_clients
end

config.after :suite do
TestHelpers::Storage::Mock.unmock_storage_clients
end

config.mock_with :rspec

config.before :each do
Expand Down
5 changes: 3 additions & 2 deletions spec/unit/queue_storage_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,11 @@ module Backend

def is_sentinel?(connection)
if ThreeScale::Backend.configuration.redis.async
connector = connection.instance_variable_get(:@redis_async)
connector = connection.instance_variable_get(:@inner).instance_variable_get(:@redis_async)
connector.instance_of?(ThreeScale::Backend::AsyncRedis::SentinelsClientACLTLS)
else
connector = connection.instance_variable_get(:@client)
connector = connection.instance_variable_get(:@inner)
.instance_variable_get(:@client)
.instance_variable_get(:@config)

!!connector&.sentinel?
Expand Down
2 changes: 1 addition & 1 deletion spec/unit/stats/cleaner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module Stats
include TestHelpers::Sequences

let(:storage) { Backend::Storage.instance }
let(:storage_instances) { [storage] }
let(:storage_instances) { storage.send(:proxied_instances) }
let(:logger) { object_double(Backend.logger) }

before do
Expand Down
6 changes: 3 additions & 3 deletions spec/unit/storage_async/pipeline_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ module Backend
module StorageAsync
describe Pipeline do
describe '.run' do
let(:endpoint) { Async::IO::Endpoint.tcp('localhost', 6379) }
let(:async_client) { Async::Redis::Client.new(endpoint) }
let(:storage) {ThreeScale::Backend::StorageAsync::Client.instance(true)}
let(:async_client) { storage.instance_variable_get(:@inner).instance_variable_get(:@redis_async) }

subject { Pipeline.new }

# Spec helpers make sure to cleanup instances of Storage, but in this
# case, we are using the client directly with a host/port so we need
# this to call flushdb().
before { async_client.flushdb! }
before { storage.flushdb }

context 'When the list of commands is empty' do
it 'returns an empty array' do
Expand Down
8 changes: 8 additions & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@
## without creating a worker first, only happens in test environment
ThreeScale::Backend::Worker.new

Test::Unit.at_start do
TestHelpers::Storage::Mock.mock_storage_clients
end

Test::Unit.at_exit do
TestHelpers::Storage::Mock.unmock_storage_clients
end

class Test::Unit::TestCase
include ThreeScale
include ThreeScale::Backend
Expand Down
165 changes: 165 additions & 0 deletions test/test_helpers/twemproxy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
require '3scale/backend/storage_async'
require '3scale/backend/storage_sync'

module TestHelpers
module Storage
# Test::Unit hooks, just include TestHelpers::Storage in a testcase if you
# do not use the global mocking with at_start/at_exit hooks.
def self.included(base)
base.singleton_class.instance_eval do
prepend Hooks
end
end

module Hooks
def startup
Mock.mock_storage_clients
super
end

def shutdown
super
Mock.unmock_storage_clients
end
end
private_constant :Hooks

module Mock
DEFAULT_TWEMPROXY_PORT = 22121

DEFAULT_NODES = %w[127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384].freeze
private_constant :DEFAULT_NODES

STORAGE_CLASSES = [
ThreeScale::Backend::StorageAsync::Client,
ThreeScale::Backend::StorageSync
].freeze
private_constant :STORAGE_CLASSES

class << self
def nodes
# Return original URL unless we are testing on a twemproxy
uri = URI(ThreeScale::Backend.configuration.redis.proxy)
return [uri.to_s] if uri.port != DEFAULT_TWEMPROXY_PORT

# Return proxied shards if testing on a twemproxy
@nodes || DEFAULT_NODES
end

def mock_storage_clients
STORAGE_CLASSES.each { |klass| mock_storage_client!(klass) }
end

def unmock_storage_clients
STORAGE_CLASSES.each { |klass| unmock_storage_client!(klass) }
end

private

def mock_storage_client!(storage_client_class)
class << storage_client_class
# ensure this does not get overwritten
begin
const_get(:RedisClientTest)
rescue NameError
else
raise "redefined RedisClientTest"
end

# a wrapper class for the Redis client used in tests so that we can
# address specific Redis instances (as compared to proxies) for flushing.
class RedisClientTest
def initialize(inner_client)
@inner = inner_client
end

def keys(*keys)
proxied_instances.map do |i|
i.keys(*keys)
end.flatten(1)
end

def flushdb
proxied_instances.map do |i|
i.flushdb
end
end

def flushall
proxied_instances.map do |i|
i.flushall
end
end

def method_missing(m, *args, **kwargs, &blk)
# define and delegate the missing method
self.class.send(:define_method, m) do |*a, **kwa, &b|
inner.send(m, *a, **kwa, &b)
end
inner.send(m, *args, **kwargs, &blk)
end

def respond_to_missing?(m)
inner.respond_to_missing? m
end

# Needed to call Redis::Namespace.new(). Used in WorkerAsync.
def respond_to?(m)
inner.respond_to?(m)
end

private

attr_reader :inner

def proxied_instances
klass = case inner
when ThreeScale::Backend::StorageAsync::Client
inner.class
when Redis
# inner is a Redis instance when using the sync storage
ThreeScale::Backend::StorageSync
else
raise 'Unknown inner storage class'
end

klass.proxied_instances
end
end
private_constant :RedisClientTest

# for testing we need to return a wrapper that catches some specific
# commands so that they are sent to shards instead to a proxy, because
# the proxy lacks support for those (these are typically commands to
# flush the contents of the database).
alias_method :orig_new, :new

def new(options)
RedisClientTest.new orig_new(options)
end

def proxied_instances
@proxied_instances ||= Mock.nodes.map do |server|
orig_new(
::ThreeScale::Backend::Storage::Helpers.config_with(
configuration.redis,
options: { url: server }))
end
end
public :proxied_instances
end
end

def unmock_storage_client!(storage_client_class)
storage_client_class.singleton_class.instance_eval do
remove_const :RedisClientTest
remove_method :new
alias_method :new, :orig_new
remove_method :orig_new
remove_method :proxied_instances
end
end
end
end
end
end
4 changes: 2 additions & 2 deletions test/unit/storage_async_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ def create_certs(alg)
end

def assert_client_config(conf, conn, test_cert_type = nil)
client = conn.instance_variable_get(:@redis_async)
client = conn.instance_variable_get(:@inner).instance_variable_get(:@redis_async)

url = URI(conf[:url])
host, port = client.endpoint.address
Expand All @@ -329,7 +329,7 @@ def assert_client_config(conf, conn, test_cert_type = nil)
end

def assert_sentinel_config(conf, conn, test_cert_type = nil)
client = conn.instance_variable_get(:@redis_async)
client = conn.instance_variable_get(:@inner).instance_variable_get(:@redis_async)
uri = URI(conf[:url] || '')
name = uri.host
role = conf[:role] || :master
Expand Down
4 changes: 2 additions & 2 deletions test/unit/storage_sync_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ def test_acl_tls
private

def assert_client_config(conf, conn)
config = conn.instance_variable_get(:@client).instance_variable_get(:@config)
config = conn.instance_variable_get(:@inner).instance_variable_get(:@client).instance_variable_get(:@config)

if conf[:url].to_s.strip.empty?
assert_equal conf[:path], config.path
Expand All @@ -330,7 +330,7 @@ def assert_client_config(conf, conn)
end

def assert_sentinel_config(conf, client)
config = client.instance_variable_get(:@client).instance_variable_get(:@config)
config = client.instance_variable_get(:@inner).instance_variable_get(:@client).instance_variable_get(:@config)
assert config.sentinel?
assert_equal URI(conf[:url]).host, config.name
assert_equal conf[:role] || :master, config.instance_variable_get(:@role)
Expand Down

0 comments on commit 9071852

Please sign in to comment.