Skip to content

Commit

Permalink
Add resolv-replace compatibility test
Browse files Browse the repository at this point in the history
While `dalli` doesn't depend on `resolv-replace`, we want to ensure it
still works if it is required by the host application.

This regression test ensures we don't break compatibility, and adds a
framework allowing us to test compatibility with other gems in the
future.
  • Loading branch information
sambostock committed Mar 7, 2024
1 parent 67942b8 commit 7ed997e
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ group :development, :test do
gem 'rubocop-performance'
gem 'rubocop-rake'
gem 'simplecov'

# For compatibility testing
gem 'resolv-replace', require: false
end

group :test do
Expand Down
48 changes: 48 additions & 0 deletions scripts/gems_smoke_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#! /usr/bin/env ruby

# frozen_string_literal: true

# This file is meant to be run in a subprocess from a test file, to test compatibility with other gems in isolation.
# If the test fails, it will exit with a non-zero status code and print the marshaled error to stdout.
# Debug information should be printed to stderr. stdout should only contain the marshaled error.

require 'minitest'
require 'dalli'

def assert_round_trip(message)
key = "random-key-#{Time.now.to_f}"
expected = Time.now.to_f.to_s
ttl = 10 # seconds

client = Dalli::Client.new(nil)
client.set(key, expected, ttl)

actual = client.get(key)

# assert_equal is not available in this context, so we implement it ourselves.
raise Minitest::Assertion.new, <<~MESSAGE unless expected == actual
#{message}.
Expected: #{expected.inspect}
Actual: #{actual.inspect}
MESSAGE
end

begin
gems = ARGV
gem_list = gems.map(&:inspect).join(', ')
raise 'No gems specified to require' if gems.empty?

assert_round_trip("Failed to round-trip key before requiring #{gem_list}")

gems.each do |gem_name|
warn "Requiring #{gem_name}..."
require gem_name
end

assert_round_trip("Failed to round-trip key after requiring #{gem_list}")
rescue Exception => e # rubocop:disable Lint/RescueException
# Marshal the exception to stdout so the parent process can raise it.
# Don't use stderr, as it might also contain other output, such as warnings.
puts Marshal.dump(e)
abort
end
39 changes: 39 additions & 0 deletions test/test_gem_compatibility.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

require_relative 'helper'
require 'open3'

describe 'gem compatibility' do
%w[
resolv-replace
socksify
].each do |gem_name|
it "passes smoke test with #{gem_name.inspect} gem required" do
memcached(:binary, 11_112) do |_, port|
# We use a separate Ruby process so we can require the gem in isolation without affecting the rest of the tests.
stdout, stderr, status = Open3.capture3(
{ 'MEMCACHED_SERVERS' => "localhost:#{port}" },
'scripts/gems_smoke_test.rb',
gem_name
)

return pass if status.success?

error = begin
# Marshal.load is fine here because we're loading what we just dumped in the subprocess.
Marshal.load(stdout) # rubocop:disable Security/MarshalLoad
rescue StandardError => e
raise <<~MESSAGE
Failed to unmarshal error from subprocess!
#{e.class}: #{e}
stdout: #{stdout}
stderr: #{stderr}
MESSAGE
end

pass if error.is_a?(Minitest::Assertion) # Increment assertion count
raise error
end
end
end
end

0 comments on commit 7ed997e

Please sign in to comment.