diff --git a/Gemfile b/Gemfile index 052aa375..2f8a4532 100644 --- a/Gemfile +++ b/Gemfile @@ -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 diff --git a/scripts/gems_smoke_test.rb b/scripts/gems_smoke_test.rb new file mode 100755 index 00000000..52b38957 --- /dev/null +++ b/scripts/gems_smoke_test.rb @@ -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 diff --git a/test/test_gem_compatibility.rb b/test/test_gem_compatibility.rb new file mode 100644 index 00000000..32882a55 --- /dev/null +++ b/test/test_gem_compatibility.rb @@ -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