#!/usr/bin/env elixir # Ranch API Test - Test the exact Ranch configuration format # Run with: elixir test_ranch_format.exs defmodule RanchFormatTest do @moduledoc """ Test different Ranch configuration formats to identify the correct one. """ require Logger def test_ranch_formats do IO.puts("=== Ranch Configuration Format Test ===\n") # Test different Ranch configuration formats test_ranch_map_format() test_ranch_list_format() test_ranch_mixed_format() IO.puts("\n=== Ranch Format Test Complete ===") end defp test_ranch_map_format do IO.puts("๐Ÿงช Testing Ranch Map Format (Recommended)...") # Modern Ranch format (map with socket_opts) ranch_opts = %{ socket_opts: [port: 9998], max_connections: 10 } IO.puts(" ๐Ÿ“‹ Format: #{inspect(ranch_opts)}") try do # This should work with modern Ranch result = validate_ranch_format(:test_map_listener, :ranch_tcp, ranch_opts, DummyProtocol, []) IO.puts(" โœ… Map format accepted by Ranch API") rescue error -> IO.puts(" โŒ Map format error: #{inspect(error)}") end IO.puts("") end defp test_ranch_list_format do IO.puts("๐Ÿงช Testing Ranch List Format (Legacy)...") # Legacy Ranch format (list) ranch_opts = [ port: 9997, max_connections: 10 ] IO.puts(" ๐Ÿ“‹ Format: #{inspect(ranch_opts)}") try do # This might work with older Ranch result = validate_ranch_format(:test_list_listener, :ranch_tcp, ranch_opts, DummyProtocol, []) IO.puts(" โœ… List format accepted by Ranch API") rescue error -> IO.puts(" โŒ List format error: #{inspect(error)}") end IO.puts("") end defp test_ranch_mixed_format do IO.puts("๐Ÿงช Testing Ranch Mixed Format (Common Mistake)...") # Wrong format - max_connections in socket_opts ranch_opts = %{ socket_opts: [port: 9996, max_connections: 10] # This is WRONG } IO.puts(" ๐Ÿ“‹ Format: #{inspect(ranch_opts)}") try do result = validate_ranch_format(:test_mixed_listener, :ranch_tcp, ranch_opts, DummyProtocol, []) IO.puts(" โš ๏ธ Mixed format accepted (unexpected)") rescue error -> IO.puts(" โŒ Mixed format error (expected): #{inspect(error)}") end IO.puts("") end defp validate_ranch_format(id, transport, opts, protocol, protocol_opts) do # This simulates what Ranch does during start_listener validation # We're not actually starting a listener, just checking the arguments # Check if Ranch module exists and has start_listener function unless function_exported?(:ranch, :start_listener, 5) do raise "Ranch module not available or different API" end # Validate argument types unless is_atom(id) do raise ArgumentError, "Listener ID must be an atom" end unless transport == :ranch_tcp or transport == :ranch_ssl do raise ArgumentError, "Transport must be :ranch_tcp or :ranch_ssl" end unless is_atom(protocol) do raise ArgumentError, "Protocol must be an atom (module name)" end unless is_list(protocol_opts) do raise ArgumentError, "Protocol opts must be a list" end # Validate Ranch opts structure case opts do %{} = map_opts -> validate_map_opts(map_opts) opts when is_list(opts) -> validate_list_opts(opts) _ -> raise ArgumentError, "Ranch opts must be a map or list" end :ok end defp validate_map_opts(opts) when is_map(opts) do # Check for required/expected keys in map format if Map.has_key?(opts, :socket_opts) do socket_opts = Map.get(opts, :socket_opts) unless is_list(socket_opts) do raise ArgumentError, "socket_opts must be a list" end # Check if max_connections is wrongly in socket_opts if Keyword.has_key?(socket_opts, :max_connections) do raise ArgumentError, "max_connections should not be in socket_opts" end end # Check max_connections at top level if Map.has_key?(opts, :max_connections) do max_conn = Map.get(opts, :max_connections) unless is_integer(max_conn) and max_conn > 0 do raise ArgumentError, "max_connections must be a positive integer" end end :ok end defp validate_list_opts(opts) when is_list(opts) do # For list format, just check if it's a proper keyword list unless Keyword.keyword?(opts) do raise ArgumentError, "Ranch opts list must be a keyword list" end :ok end def test_actual_ranch_start do IO.puts("๐Ÿงช Testing Actual Ranch Start (will fail gracefully)...") # Test with a non-existent port to see the actual Ranch error ranch_opts = %{ socket_opts: [port: 99999], # High port number unlikely to be in use max_connections: 1 } try do result = :ranch.start_listener( :test_actual_ranch, :ranch_tcp, ranch_opts, NonExistentProtocol, # This will cause an error, which is expected [] ) IO.puts(" โš ๏ธ Unexpected success: #{inspect(result)}") # Clean up if it somehow worked :ranch.stop_listener(:test_actual_ranch) rescue error -> case error do %UndefinedFunctionError{function: :start_listener} -> IO.puts(" โŒ Ranch not available or different API") %ArgumentError{message: message} -> if String.contains?(message, "badarg") do IO.puts(" โŒ Ranch configuration format error (badarg)") else IO.puts(" โŒ Ranch argument error: #{message}") end error -> IO.puts(" โœ… Ranch accepted format, protocol error expected: #{inspect(error)}") end end end end # Run the tests IO.puts("Starting Ranch configuration format tests...") RanchFormatTest.test_ranch_formats() RanchFormatTest.test_actual_ranch_start() IO.puts("\n๐Ÿ“‹ Summary:") IO.puts("- Map format with socket_opts should work with modern Ranch") IO.puts("- max_connections must be at top level, not in socket_opts") IO.puts("- socket_opts should only contain socket-specific options") # Check Ranch version if possible try do ranch_vsn = Application.spec(:ranch, :vsn) IO.puts("- Ranch version: #{ranch_vsn}") rescue _ -> IO.puts("- Ranch version: unknown") end