CrackedRuby logo

CrackedRuby

Gem Creation

Overview

Ruby gems provide the primary mechanism for packaging and distributing Ruby libraries. The gem system centers around the Gem::Specification class, which defines metadata, dependencies, and file inclusion patterns. Ruby's gem build command processes gemspec files into distributable .gem archives, while gem push publishes to repositories.

The gem creation process involves three core files: the gemspec specification, a library structure following Ruby's load path conventions, and optional executables in the bin directory. The Gem::Specification object accepts metadata through method calls or block syntax, validates dependencies, and handles version constraints.

# basic.gemspec
Gem::Specification.new do |spec|
  spec.name = "my_gem"
  spec.version = "1.0.0"
  spec.authors = ["Author Name"]
  spec.files = Dir["lib/**/*.rb"]
  spec.require_paths = ["lib"]
end

Ruby loads gems through the $LOAD_PATH mechanism, requiring the gem's require_paths directories. The gem system automatically manages version resolution, dependency loading, and file discovery. When multiple gem versions exist, Ruby selects the latest unless version constraints specify otherwise.

# lib/my_gem.rb
module MyGem
  VERSION = "1.0.0"
  
  def self.hello
    "Hello from gem"
  end
end

The bundle gem command generates standard gem scaffolding with conventional directory structure, gemspec template, and basic configuration files. This approach ensures consistency with Ruby community practices and RubyGems requirements.

Basic Usage

Creating a gem starts with directory structure establishment and gemspec definition. The conventional layout places library code under lib/, with the main file matching the gem name. Ruby's require system loads lib/gem_name.rb when code calls require 'gem_name'.

# Generate gem scaffold
system("bundle gem example_calculator")

# example_calculator.gemspec
require_relative "lib/example_calculator/version"

Gem::Specification.new do |spec|
  spec.name = "example_calculator"
  spec.version = ExampleCalculator::VERSION
  spec.authors = ["Developer"]
  spec.email = ["dev@example.com"]
  spec.summary = "Simple mathematical operations"
  spec.description = "Provides basic arithmetic with error handling"
  spec.homepage = "https://github.com/user/example_calculator"
  spec.license = "MIT"
  
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
    `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
  end
  
  spec.bindir = "exe"
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
  spec.require_paths = ["lib"]
  
  spec.add_dependency "activesupport", ">= 5.0"
  spec.add_development_dependency "rake", "~> 13.0"
  spec.add_development_dependency "minitest", "~> 5.0"
end

The gemspec's files specification determines which files include in the built gem. The git ls-files approach includes version-controlled files while excluding test directories. Ruby processes this during gem build to create the final package.

# lib/example_calculator.rb
require "active_support/core_ext/numeric/conversions"
require_relative "example_calculator/version"
require_relative "example_calculator/operations"

module ExampleCalculator
  class Error < StandardError; end
  class DivisionByZeroError < Error; end
  
  extend Operations
end

Building the gem requires running gem build with the gemspec file. This command validates the specification, processes file inclusions, and creates a .gem archive. Ruby stores built gems in the current directory unless configured otherwise.

# Build process
system("gem build example_calculator.gemspec")
# => Successfully built RubyGem
# => Name: example_calculator
# => Version: 1.0.0
# => File: example_calculator-1.0.0.gem

Installation happens through gem install with the built gem file or directly from repositories. Ruby processes dependencies automatically, downloading required gems and their transitive dependencies. The --local flag installs from local files without repository access.

# Local installation
system("gem install ./example_calculator-1.0.0.gem")

# Usage after installation
require "example_calculator"
result = ExampleCalculator.add(5, 3)
# => 8

Advanced Usage

Complex gems require sophisticated configuration through gemspec extensions, conditional dependencies, and platform-specific handling. The Gem::Specification supports conditional logic for different Ruby versions, operating systems, and runtime environments.

# advanced_gem.gemspec
Gem::Specification.new do |spec|
  spec.name = "advanced_processor"
  spec.version = "2.1.0"
  
  # Platform-specific dependencies
  if RUBY_PLATFORM =~ /java/
    spec.add_dependency "jruby-openssl"
  else
    spec.add_dependency "openssl", ">= 2.0"
  end
  
  # Ruby version constraints
  spec.required_ruby_version = ">= 2.7.0"
  spec.required_rubygems_version = ">= 3.0.0"
  
  # Complex file patterns
  spec.files = Dir[
    "lib/**/*.rb",
    "ext/**/*.{c,h,rb}",
    "data/**/*.{json,yml}",
    "README*",
    "LICENSE*",
    "CHANGELOG*"
  ].reject { |f| f.include?("spec") || f.include?("test") }
  
  # Multiple executables
  spec.executables = ["processor", "batch-processor", "processor-daemon"]
  
  # Extensions for C code
  spec.extensions = ["ext/native/extconf.rb"]
  
  # Post-install message
  spec.post_install_message = "Run 'processor --help' to get started"
end

Native extensions integrate C code into gems through extconf.rb configuration files. Ruby processes these during installation, compiling C sources and linking against system libraries. The extension mechanism requires careful platform handling and dependency management.

# ext/native/extconf.rb
require "mkmf"

# Check for required headers
unless have_header("openssl/ssl.h")
  abort "OpenSSL headers not found"
end

# Check for libraries
unless have_library("ssl", "SSL_new")
  abort "OpenSSL library not found"
end

# Configure build flags
$CFLAGS << " -O3 -Wall"
$LDFLAGS << " -lssl -lcrypto"

# Create Makefile
create_makefile("native/processor_ext")

Multi-platform gems handle different operating systems through platform-specific gemspecs and conditional code paths. Ruby's RUBY_PLATFORM constant identifies the target platform, enabling platform-appropriate behavior selection.

# lib/advanced_processor/platform.rb
module AdvancedProcessor
  module Platform
    case RUBY_PLATFORM
    when /mswin|mingw|cygwin/
      WINDOWS = true
      SEPARATOR = "\\"
      
      def self.memory_info
        `wmic OS get TotalVisibleMemorySize /value`.split("=").last.to_i
      end
      
    when /linux/
      LINUX = true
      SEPARATOR = "/"
      
      def self.memory_info
        File.read("/proc/meminfo")[/MemTotal:\s+(\d+)/, 1].to_i
      end
      
    when /darwin/
      MACOS = true
      SEPARATOR = "/"
      
      def self.memory_info
        `sysctl -n hw.memsize`.to_i / 1024
      end
      
    else
      UNIX = true
      SEPARATOR = "/"
      
      def self.memory_info
        # Generic Unix fallback
        1024 * 1024 # 1GB default
      end
    end
  end
end

Complex gems often implement plugin systems through dynamic loading and module extension. Ruby's const_missing and method_missing hooks enable flexible plugin discovery and registration mechanisms.

# lib/advanced_processor/plugin_system.rb
module AdvancedProcessor
  class PluginSystem
    @plugins = {}
    @loaded_paths = Set.new
    
    class << self
      attr_reader :plugins
      
      def register(name, plugin_class)
        @plugins[name.to_sym] = plugin_class
      end
      
      def load_plugins(directory)
        return if @loaded_paths.include?(directory)
        
        Dir.glob(File.join(directory, "**/*.rb")).each do |file|
          require_relative file
        rescue LoadError => e
          warn "Failed to load plugin #{file}: #{e.message}"
        end
        
        @loaded_paths << directory
      end
      
      def const_missing(name)
        plugin_name = name.to_s.underscore.to_sym
        if plugin_class = @plugins[plugin_name]
          const_set(name, plugin_class)
        else
          super
        end
      end
    end
  end
end

Error Handling & Debugging

Gem creation encounters various error categories: specification validation, dependency resolution, build failures, and runtime conflicts. Ruby's gem system provides specific exception classes for different failure modes, requiring targeted handling strategies.

The Gem::InvalidSpecificationException occurs when gemspec validation fails during build or installation. Common causes include missing required fields, invalid version formats, or malformed dependency specifications.

# Debugging gemspec issues
begin
  spec = Gem::Specification.load("problematic.gemspec")
  spec.validate
rescue Gem::InvalidSpecificationException => e
  puts "Specification errors:"
  e.message.split("\n").each { |error| puts "  - #{error}" }
  
  # Common fixes
  if e.message.include?("version")
    puts "Fix: Ensure version follows semantic versioning (x.y.z)"
  end
  
  if e.message.include?("email")
    puts "Fix: Provide valid author email address"
  end
  
  if e.message.include?("files")
    puts "Fix: Verify all specified files exist"
    puts "Missing files:"
    spec.files.each do |file|
      puts "  - #{file}" unless File.exist?(file)
    end
  end
end

Dependency conflicts arise when gem versions cannot satisfy all requirements simultaneously. Ruby's dependency resolver attempts to find compatible versions, failing when constraints create unsolvable scenarios.

# Dependency debugging tools
module GemDebugger
  def self.analyze_conflicts(gem_name)
    spec = Gem::Specification.find_by_name(gem_name)
    
    puts "Analyzing dependencies for #{gem_name} #{spec.version}"
    
    spec.dependencies.each do |dep|
      puts "\n#{dep.name} #{dep.requirement}"
      
      available = Gem::Specification.find_all_by_name(dep.name)
      compatible = available.select { |s| dep.match?(s.name, s.version) }
      
      if compatible.empty?
        puts "  ERROR: No compatible versions found"
        puts "  Available: #{available.map(&:version).join(', ')}"
      else
        puts "  Compatible: #{compatible.map(&:version).join(', ')}"
      end
      
      # Check for transitive conflicts
      compatible.each do |compat_spec|
        compat_spec.dependencies.each do |trans_dep|
          next if trans_dep.name == gem_name
          
          trans_available = Gem::Specification.find_all_by_name(trans_dep.name)
          trans_compatible = trans_available.select { |s| trans_dep.match?(s.name, s.version) }
          
          if trans_compatible.empty?
            puts "    WARNING: Transitive dependency #{trans_dep.name} #{trans_dep.requirement} not satisfiable"
          end
        end
      end
    end
  rescue Gem::LoadError => e
    puts "ERROR: Cannot find gem #{gem_name}: #{e.message}"
  end
end

Build failures often stem from missing system dependencies, incorrect file paths, or platform incompatibilities. The gem build process provides limited error context, requiring systematic debugging approaches.

# Build validation script
class BuildValidator
  def self.check_gemspec(gemspec_path)
    errors = []
    warnings = []
    
    begin
      spec = Gem::Specification.load(gemspec_path)
    rescue => e
      errors << "Failed to load gemspec: #{e.message}"
      return { errors: errors, warnings: warnings }
    end
    
    # Validate required fields
    required_fields = [:name, :version, :authors, :files, :require_paths]
    required_fields.each do |field|
      if spec.send(field).nil? || spec.send(field).empty?
        errors << "Missing required field: #{field}"
      end
    end
    
    # Check file existence
    spec.files.each do |file|
      unless File.exist?(file)
        if file.include?("README") || file.include?("LICENSE")
          warnings << "Recommended file missing: #{file}"
        else
          errors << "Required file missing: #{file}"
        end
      end
    end
    
    # Validate version format
    unless spec.version.to_s =~ /\A\d+\.\d+\.\d+/
      warnings << "Version should follow semantic versioning (x.y.z)"
    end
    
    # Check for common mistakes
    if spec.files.any? { |f| f.include?("test/") || f.include?("spec/") }
      warnings << "Test files included in gem package"
    end
    
    if spec.executables.any? && spec.bindir != "exe" && spec.bindir != "bin"
      warnings << "Non-standard bin directory: #{spec.bindir}"
    end
    
    { errors: errors, warnings: warnings }
  end
  
  def self.report(gemspec_path)
    result = check_gemspec(gemspec_path)
    
    if result[:errors].any?
      puts "ERRORS (must fix):"
      result[:errors].each { |error| puts "#{error}" }
    end
    
    if result[:warnings].any?
      puts "WARNINGS (should fix):"
      result[:warnings].each { |warning| puts "#{warning}" }
    end
    
    puts "✓ Build validation passed" if result[:errors].empty? && result[:warnings].empty?
    
    result[:errors].empty?
  end
end

Runtime debugging requires tracking gem loading, version conflicts, and require path issues. Ruby's $LOADED_FEATURES and Gem.loaded_specs provide introspection into the current gem environment.

# Runtime gem debugging
module GemRuntime
  def self.debug_loading
    puts "Loaded gems:"
    Gem.loaded_specs.each do |name, spec|
      puts "  #{name} #{spec.version} (#{spec.full_gem_path})"
    end
    
    puts "\nLoad path conflicts:"
    conflicts = find_load_path_conflicts
    conflicts.each do |path, sources|
      puts "  #{path}:"
      sources.each { |source| puts "    - #{source}" }
    end
  end
  
  private
  
  def self.find_load_path_conflicts
    path_sources = Hash.new { |h, k| h[k] = [] }
    
    $LOAD_PATH.each do |path|
      if path.include?("gems/")
        gem_match = path.match(/gems\/([^\/]+)-([^\/]+)/)
        if gem_match
          path_sources[gem_match[1]] << "#{gem_match[1]}-#{gem_match[2]} (#{path})"
        end
      else
        path_sources["system"] << path
      end
    end
    
    path_sources.select { |_, sources| sources.length > 1 }
  end
end

Production Patterns

Production gem deployment requires version management, security considerations, and distribution strategies. Ruby gems in production environments face unique challenges including dependency resolution, security updates, and compatibility maintenance across deployment targets.

Version management follows semantic versioning principles with automation through continuous integration. The versioning strategy affects dependency resolution and upgrade paths for consuming applications.

# lib/production_gem/version.rb
module ProductionGem
  # Semantic versioning with build metadata
  MAJOR = 2
  MINOR = 3
  PATCH = 1
  BUILD = ENV["BUILD_NUMBER"] || "dev"
  
  VERSION = [MAJOR, MINOR, PATCH].join(".")
  VERSION_WITH_BUILD = [VERSION, BUILD].join("-")
  
  # Release metadata
  RELEASE_DATE = Date.parse("2024-01-15")
  RUBY_REQUIREMENT = ">= 2.7.0"
  
  def self.compatible?(ruby_version)
    Gem::Requirement.new(RUBY_REQUIREMENT).satisfied_by?(Gem::Version.new(ruby_version))
  end
  
  def self.release_info
    {
      version: VERSION,
      build: BUILD,
      released: RELEASE_DATE,
      ruby_required: RUBY_REQUIREMENT,
      compatible: compatible?(RUBY_VERSION)
    }
  end
end

Security hardening involves dependency auditing, vulnerability scanning, and secure distribution practices. Production gems require regular security assessments and rapid response capabilities for vulnerability remediation.

# security/audit.rb
require "bundler/audit"
require "json"

class SecurityAuditor
  def self.run_comprehensive_audit
    report = {
      timestamp: Time.now.iso8601,
      ruby_version: RUBY_VERSION,
      bundler_version: Bundler::VERSION,
      vulnerabilities: [],
      advisories: [],
      recommendations: []
    }
    
    # Bundler audit
    begin
      Bundler::Audit::Database.update!
      scanner = Bundler::Audit::Scanner.new
      
      scanner.scan do |result|
        case result
        when Bundler::Audit::Scanner::InsecureSource
          report[:vulnerabilities] << {
            type: "insecure_source",
            source: result.source,
            severity: "high"
          }
          
        when Bundler::Audit::Scanner::UnpatchedGem
          report[:vulnerabilities] << {
            type: "unpatched_gem",
            gem: result.gem.name,
            version: result.gem.version.to_s,
            advisory: result.advisory.id,
            severity: result.advisory.criticality,
            title: result.advisory.title,
            patched_versions: result.advisory.patched_versions.map(&:to_s)
          }
        end
      end
    rescue => e
      report[:errors] = ["Audit failed: #{e.message}"]
    end
    
    # Custom security checks
    check_file_permissions(report)
    check_sensitive_data(report)
    
    generate_report(report)
  end
  
  private
  
  def self.check_file_permissions(report)
    sensitive_files = ["lib/**/*.rb", "bin/*", "exe/*"]
    
    Dir.glob(sensitive_files).each do |file|
      mode = File.stat(file).mode & 0777
      if mode & 0002 != 0  # World writable
        report[:vulnerabilities] << {
          type: "file_permissions",
          file: file,
          mode: mode.to_s(8),
          severity: "medium",
          recommendation: "Remove world write permissions"
        }
      end
    end
  end
  
  def self.check_sensitive_data(report)
    patterns = {
      api_key: /(?:api[_-]?key|access[_-]?token)\s*[:=]\s*['"][^'"]+['"]/i,
      password: /password\s*[:=]\s*['"][^'"]+['"]/i,
      secret: /secret\s*[:=]\s*['"][^'"]+['"]/i
    }
    
    Dir.glob("lib/**/*.rb").each do |file|
      content = File.read(file)
      patterns.each do |type, pattern|
        if content.match(pattern)
          report[:vulnerabilities] << {
            type: "sensitive_data",
            subtype: type,
            file: file,
            severity: "high",
            recommendation: "Move sensitive data to environment variables"
          }
        end
      end
    end
  end
  
  def self.generate_report(report)
    puts "Security Audit Report - #{report[:timestamp]}"
    puts "Ruby: #{report[:ruby_version]} | Bundler: #{report[:bundler_version]}"
    puts
    
    if report[:vulnerabilities].empty?
      puts "✓ No vulnerabilities found"
    else
      puts "#{report[:vulnerabilities].length} vulnerabilities found:"
      
      report[:vulnerabilities].group_by { |v| v[:severity] }.each do |severity, vulns|
        puts "\n#{severity.upcase} (#{vulns.length}):"
        vulns.each do |vuln|
          puts "  - #{vuln[:type]}: #{vuln[:gem] || vuln[:file]}"
          puts "    #{vuln[:title] || vuln[:recommendation]}"
        end
      end
    end
    
    # Save detailed report
    File.write("security_report.json", JSON.pretty_generate(report))
    report[:vulnerabilities].empty?
  end
end

Distribution strategies include multiple repository deployment, CDN integration, and mirror synchronization. Production gems require reliable distribution infrastructure with fallback mechanisms and geographic distribution.

# deployment/publisher.rb
class GemPublisher
  REPOSITORIES = {
    rubygems: {
      host: "https://rubygems.org",
      api_key_env: "RUBYGEMS_API_KEY",
      primary: true
    },
    github: {
      host: "https://rubygems.pkg.github.com/company",
      api_key_env: "GITHUB_TOKEN",
      private: true
    },
    internal: {
      host: "https://gems.internal.company.com",
      api_key_env: "INTERNAL_GEMS_KEY",
      private: true
    }
  }.freeze
  
  def self.publish_multi_target(gem_file, targets: [:internal, :github])
    results = {}
    
    targets.each do |target|
      config = REPOSITORIES[target]
      next unless config
      
      begin
        result = publish_to_repository(gem_file, config)
        results[target] = result
        puts "✓ Published to #{target}: #{result[:version]}"
      rescue => e
        results[target] = { error: e.message }
        puts "✗ Failed to publish to #{target}: #{e.message}"
      end
    end
    
    # Only publish to public repository if all private targets succeed
    if targets.include?(:rubygems) && results.values.none? { |r| r[:error] }
      begin
        result = publish_to_repository(gem_file, REPOSITORIES[:rubygems])
        results[:rubygems] = result
        puts "✓ Published to RubyGems: #{result[:version]}"
      rescue => e
        results[:rubygems] = { error: e.message }
        puts "✗ Failed to publish to RubyGems: #{e.message}"
      end
    end
    
    results
  end
  
  private
  
  def self.publish_to_repository(gem_file, config)
    api_key = ENV[config[:api_key_env]]
    raise "Missing API key: #{config[:api_key_env]}" unless api_key
    
    cmd = ["gem", "push", gem_file]
    cmd += ["--host", config[:host]] if config[:host] != "https://rubygems.org"
    cmd += ["--key", api_key]
    
    output = `#{cmd.join(" ")} 2>&1`
    success = $?.success?
    
    unless success
      raise "Push failed: #{output}"
    end
    
    # Extract version from gem filename
    version = gem_file[/-(\d+\.\d+\.\d+)\.gem$/, 1]
    
    {
      version: version,
      repository: config[:host],
      timestamp: Time.now.iso8601,
      output: output.strip
    }
  end
end

Testing Strategies

Gem testing requires comprehensive strategies covering unit tests, integration scenarios, version compatibility, and platform verification. Ruby gem testing faces unique challenges including dependency isolation, version matrix testing, and cross-platform validation.

Unit testing focuses on gem functionality isolation with careful attention to dependency mocking and state management. Ruby's testing frameworks provide gem-specific testing patterns and assertion methods.

# test/test_helper.rb
require "minitest/autorun"
require "minitest/pride"
require "mocha/minitest"

# Test gem in isolation
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
require "my_gem"

class MyGemTest < Minitest::Test
  def setup
    @original_env = ENV.to_hash
    # Reset gem state between tests
    MyGem.reset! if MyGem.respond_to?(:reset!)
  end
  
  def teardown
    ENV.replace(@original_env)
    # Clean up any global state
    MyGem.instance_variables.each do |var|
      MyGem.remove_instance_variable(var)
    end
  end
end

# Dependency isolation helpers
module TestHelpers
  def with_isolated_load_path
    original_load_path = $LOAD_PATH.dup
    original_loaded_features = $LOADED_FEATURES.dup
    
    yield
  ensure
    $LOAD_PATH.replace(original_load_path)
    $LOADED_FEATURES.replace(original_loaded_features)
  end
  
  def mock_gem_dependency(gem_name, version = "1.0.0")
    mock_spec = mock("gem_spec")
    mock_spec.stubs(:name).returns(gem_name)
    mock_spec.stubs(:version).returns(Gem::Version.new(version))
    
    Gem::Specification.stubs(:find_by_name).with(gem_name).returns(mock_spec)
    
    yield
  ensure
    Gem::Specification.unstub(:find_by_name)
  end
end

Integration testing validates gem behavior within realistic application contexts, including Rails applications, Rack middleware, and CLI environments. These tests verify gem loading, configuration, and interaction patterns.

# test/integration/rails_integration_test.rb
require "test_helper"
require "rails"
require "active_record"

class RailsIntegrationTest < MyGemTest
  def setup
    super
    create_test_rails_app
  end
  
  def teardown
    cleanup_test_rails_app
    super
  end
  
  def test_gem_initializes_with_rails
    with_test_rails_app do |app|
      app.initialize!
      
      assert MyGem.configured?
      assert_equal "test", MyGem.environment
      assert_includes MyGem.middleware_stack, MyGem::RailsMiddleware
    end
  end
  
  def test_activerecord_integration
    with_test_rails_app do |app|
      app.initialize!
      
      # Test ActiveRecord extension
      model_class = Class.new(ActiveRecord::Base) do
        self.table_name = "test_models"
        include MyGem::ModelExtensions
      end
      
      instance = model_class.new(name: "test")
      assert_respond_to instance, :my_gem_method
      assert_equal "processed: test", instance.my_gem_method
    end
  end
  
  private
  
  def create_test_rails_app
    @test_app_dir = Dir.mktmpdir("my_gem_test_rails")
    
    # Minimal Rails application
    @app_file = File.join(@test_app_dir, "application.rb")
    File.write(@app_file, <<~RUBY)
      require "rails/all"
      require "my_gem"
      
      class TestApp < Rails::Application
        config.eager_load = false
        config.active_support.deprecation = :stderr
        config.secret_key_base = "test_secret"
        
        # Configure database
        config.active_record.database_adapter = "sqlite3"
        config.active_record.database = ":memory:"
      end
      
      Rails.application.initialize!
      
      # Create test table
      ActiveRecord::Schema.define do
        create_table :test_models do |t|
          t.string :name
          t.timestamps
        end
      end
    RUBY
  end
  
  def with_test_rails_app
    original_dir = Dir.pwd
    Dir.chdir(@test_app_dir)
    
    load @app_file
    yield Rails.application
  ensure
    Dir.chdir(original_dir)
    Rails.application = nil if defined?(Rails.application)
  end
  
  def cleanup_test_rails_app
    FileUtils.rm_rf(@test_app_dir) if @test_app_dir
  end
end

Version compatibility testing ensures gem functionality across supported Ruby versions, gem dependencies, and platform combinations. Matrix testing requires automated infrastructure and systematic validation approaches.

# test/compatibility/version_matrix_test.rb
class VersionMatrixTest < MyGemTest
  RUBY_VERSIONS = ["2.7.0", "3.0.0", "3.1.0", "3.2.0"].freeze
  DEPENDENCY_VERSIONS = {
    "activesupport" => ["5.2.0", "6.0.0", "6.1.0", "7.0.0"],
    "concurrent-ruby" => ["1.0.0", "1.1.0", "1.2.0"]
  }.freeze
  
  def test_version_compatibility_matrix
    results = {}
    
    RUBY_VERSIONS.each do |ruby_version|
      results[ruby_version] = test_ruby_version_compatibility(ruby_version)
    end
    
    generate_compatibility_report(results)
    assert results.values.all? { |r| r[:compatible] }
  end
  
  private
  
  def test_ruby_version_compatibility(ruby_version)
    return { compatible: false, reason: "Cannot test current Ruby version" } if RUBY_VERSION != ruby_version
    
    compatibility_results = {
      compatible: true,
      ruby_version: ruby_version,
      dependency_tests: {},
      feature_tests: {}
    }
    
    # Test core functionality
    begin
      require "my_gem"
      MyGem.hello
      compatibility_results[:feature_tests][:basic] = { status: :pass }
    rescue => e
      compatibility_results[:compatible] = false
      compatibility_results[:feature_tests][:basic] = { status: :fail, error: e.message }
    end
    
    # Test with different dependency versions
    DEPENDENCY_VERSIONS.each do |gem_name, versions|
      compatibility_results[:dependency_tests][gem_name] = test_dependency_versions(gem_name, versions)
    end
    
    compatibility_results
  end
  
  def test_dependency_versions(gem_name, versions)
    version_results = {}
    
    versions.each do |version|
      begin
        with_isolated_load_path do
          mock_gem_dependency(gem_name, version) do
            # Test gem functionality with this dependency version
            require "my_gem"
            MyGem.hello
            version_results[version] = { status: :pass }
          end
        end
      rescue => e
        version_results[version] = { status: :fail, error: e.message }
      end
    end
    
    version_results
  end
  
  def generate_compatibility_report(results)
    puts "\nVersion Compatibility Report"
    puts "=" * 50
    
    results.each do |ruby_version, result|
      status = result[:compatible] ? "" : ""
      puts "#{status} Ruby #{ruby_version}"
      
      unless result[:compatible]
        puts "  Reason: #{result[:reason]}"
      end
      
      if result[:dependency_tests]
        result[:dependency_tests].each do |dep_name, dep_results|
          compatible_versions = dep_results.select { |_, r| r[:status] == :pass }.keys
          puts "  #{dep_name}: #{compatible_versions.join(', ')}"
        end
      end
    end
  end
end

Performance and memory testing validates gem efficiency under realistic load conditions. These tests identify performance regressions and memory leaks that could affect production deployments.

# test/performance/benchmark_test.rb
require "benchmark"
require "memory_profiler"

class BenchmarkTest < MyGemTest
  def test_performance_benchmarks
    puts "\nPerformance Benchmarks"
    puts "=" * 30
    
    # CPU performance
    Benchmark.bm(20) do |x|
      x.report("basic_operation") { 1000.times { MyGem.basic_operation("test") } }
      x.report("complex_operation") { 100.times { MyGem.complex_operation(large_data_set) } }
      x.report("concurrent_ops") { test_concurrent_performance }
    end
    
    # Memory usage
    memory_report = MemoryProfiler.report do
      1000.times { MyGem.basic_operation("test") }
    end
    
    puts "\nMemory Usage:"
    puts "Total allocated: #{memory_report.total_allocated} objects"
    puts "Total retained: #{memory_report.total_retained} objects"
    
    # Assert performance thresholds
    assert_performance_thresholds
  end
  
  private
  
  def test_concurrent_performance
    threads = 10.times.map do
      Thread.new do
        100.times { MyGem.thread_safe_operation("test") }
      end
    end
    
    threads.each(&:join)
  end
  
  def large_data_set
    @large_data_set ||= (1..10000).to_a.map { |i| "data_item_#{i}" }
  end
  
  def assert_performance_thresholds
    # Basic operation should complete in under 1ms per call
    time = Benchmark.realtime { 1000.times { MyGem.basic_operation("test") } }
    assert time < 1.0, "Basic operation too slow: #{time}s for 1000 calls"
    
    # Memory usage should be reasonable
    memory_report = MemoryProfiler.report do
      1000.times { MyGem.basic_operation("test") }
    end
    
    assert memory_report.total_allocated < 50000, "Too many objects allocated: #{memory_report.total_allocated}"
  end
end

Reference

Core Classes and Modules

Class/Module Purpose Key Methods
Gem::Specification Gem metadata definition #new, #validate, #files=, #dependencies
Gem::Version Version parsing and comparison #new, #<=>, #prerelease?, #segments
Gem::Requirement Version constraint specification #new, #satisfied_by?, #as_list
Gem::Dependency Dependency declaration #new, #match?, #runtime?, #development?
Gem::Platform Platform identification #new, #=~, #cpu, #os, #version

Gemspec Specification Fields

Field Type Required Description
name String Yes Gem identifier for installation and require
version String/Version Yes Version following semantic versioning
authors Array Yes Author names for gem attribution
email Array No Contact emails for maintainers
summary String Yes Brief one-line gem description
description String No Detailed gem functionality explanation
homepage String No Project website or repository URL
license String No License identifier (MIT, GPL-3.0, etc.)
files Array Yes Files included in built gem package
executables Array No Command-line executables to install
require_paths Array Yes Directories added to $LOAD_PATH
dependencies Array No Runtime and development dependencies

Version Constraint Operators

Operator Example Meaning
= = 1.2.3 Exact version match
> > 1.0.0 Greater than version
>= >= 2.0.0 Greater than or equal
< < 3.0.0 Less than version
<= <= 2.5.0 Less than or equal
~> ~> 1.2.0 Compatible version (>= 1.2.0, < 1.3.0)
!= != 1.5.0 Not equal to version

Command Line Tools

Command Parameters Purpose
gem build gemspec_file Build gem from specification
gem install gem_file [options] Install gem locally
gem push gem_file [--host URL] Publish to repository
gem yank name -v version Remove from repository
gem specification gem_name Display gem metadata
gem dependency gem_name Show dependency tree
gem cleanup [gem_name] Remove old versions
bundle gem name [options] Generate gem scaffold

Platform Identifiers

Platform Ruby Constant Description
ruby RUBY_PLATFORM =~ /ruby/ Pure Ruby implementation
java RUBY_PLATFORM =~ /java/ JRuby implementation
x86_64-linux RUBY_PLATFORM =~ /linux/ 64-bit Linux systems
x86_64-darwin RUBY_PLATFORM =~ /darwin/ 64-bit macOS systems
x64-mingw32 RUBY_PLATFORM =~ /mingw/ 64-bit Windows MinGW
mswin RUBY_PLATFORM =~ /mswin/ Windows Visual Studio

Exception Hierarchy

StandardError
├── Gem::Exception
│   ├── Gem::CommandLineError
│   ├── Gem::DependencyError
│   ├── Gem::DocumentError
│   ├── Gem::EndOfYAMLException
│   ├── Gem::FilePermissionError
│   ├── Gem::FormatException
│   ├── Gem::GemNotFoundException
│   ├── Gem::InstallError
│   ├── Gem::InvalidSpecificationException
│   ├── Gem::LoadError
│   ├── Gem::OperationNotSupportedError
│   ├── Gem::RemoteError
│   ├── Gem::RemoteInstallationCancelled
│   ├── Gem::RemoteInstallationSkipped
│   ├── Gem::RemoteSourceException
│   ├── Gem::RequirementParseError
│   ├── Gem::RuntimeRequirementNotMetError
│   ├── Gem::SystemExitException
│   ├── Gem::UnsatisfiableDependencyError
│   └── Gem::VerificationError

Environment Variables

Variable Purpose Example
GEM_HOME Primary gem installation directory /home/user/.gem/ruby/3.0.0
GEM_PATH Gem search path directories /usr/local/lib/ruby/gems/3.0.0
RUBYGEMS_HOST Default gem repository URL https://rubygems.org
RUBYGEMS_API_KEY Authentication for gem push API key string
GEM_SPEC_CACHE Specification cache directory /tmp/gem_spec_cache

File Structure Conventions

gem_name/
├── lib/
│   ├── gem_name.rb              # Main require file
│   └── gem_name/
│       ├── version.rb           # Version constant
│       └── *.rb                 # Implementation files
├── bin/ or exe/
│   └── executable_name          # Command-line executables
├── test/ or spec/
│   └── **/*_test.rb            # Test files (excluded from gem)
├── gem_name.gemspec             # Gem specification
├── Gemfile                      # Development dependencies
├── Rakefile                     # Build tasks
├── README.md                    # Documentation
├── LICENSE                      # License text
└── CHANGELOG.md                 # Version history