test/unit/server_tests.rb in sanford-0.10.1 vs test/unit/server_tests.rb in sanford-0.11.0

- old
+ new

@@ -1,48 +1,529 @@ require 'assert' require 'sanford/server' -class Sanford::Server +require 'dat-tcp/server_spy' +require 'ns-options/assert_macros' +require 'sanford/route' +require 'sanford-protocol/fake_connection' +require 'test/support/fake_server_connection' +module Sanford::Server + class UnitTests < Assert::Context desc "Sanford::Server" setup do - @server = Sanford::Server.new(TestHost, :keep_alive => true) + @server_class = Class.new do + include Sanford::Server + end end + subject{ @server_class } + + should have_imeths :configuration + should have_imeths :name, :ip, :port, :pid_file + should have_imeths :receives_keep_alive + should have_imeths :verbose_logging, :logger + should have_imeths :init, :error + should have_imeths :router + should have_imeths :template_source, :build_template_source + + should "know its configuration" do + config = subject.configuration + assert_instance_of Configuration, config + assert_same config, subject.configuration + end + + should "allow reading/writing its configuration name" do + new_name = Factory.string + subject.name(new_name) + assert_equal new_name, subject.configuration.name + assert_equal new_name, subject.name + end + + should "allow reading/writing its configuration ip" do + new_ip = Factory.string + subject.ip(new_ip) + assert_equal new_ip, subject.configuration.ip + assert_equal new_ip, subject.ip + end + + should "allow reading/writing its configuration port" do + new_port = Factory.integer + subject.port(new_port) + assert_equal new_port, subject.configuration.port + assert_equal new_port, subject.port + end + + should "allow reading/writing its configuration pid file" do + new_pid_file = Factory.string + subject.pid_file(new_pid_file) + expected = Pathname.new(new_pid_file) + assert_equal expected, subject.configuration.pid_file + assert_equal expected, subject.pid_file + end + + should "allow reading/writing its configuration receives keep alive" do + new_keep_alive = Factory.boolean + subject.receives_keep_alive(new_keep_alive) + assert_equal new_keep_alive, subject.configuration.receives_keep_alive + assert_equal new_keep_alive, subject.receives_keep_alive + end + + should "allow reading/writing its configuration verbose logging" do + new_verbose = Factory.boolean + subject.verbose_logging(new_verbose) + assert_equal new_verbose, subject.configuration.verbose_logging + assert_equal new_verbose, subject.verbose_logging + end + + should "allow reading/writing its configuration logger" do + new_logger = Factory.string + subject.logger(new_logger) + assert_equal new_logger, subject.configuration.logger + assert_equal new_logger, subject.logger + end + + should "allow adding init procs to its configuration" do + new_init_proc = proc{ Factory.string } + subject.init(&new_init_proc) + assert_includes new_init_proc, subject.configuration.init_procs + end + + should "allow adding error procs to its configuration" do + new_error_proc = proc{ Factory.string } + subject.error(&new_error_proc) + assert_includes new_error_proc, subject.configuration.error_procs + end + + should "allow reading/writing its configuration router" do + new_router = Factory.string + subject.router(new_router) + assert_equal new_router, subject.configuration.router + assert_equal new_router, subject.router + end + + should "allow configuring the router by passing a block to `router`" do + new_router = Factory.string + + block_scope = nil + subject.router(new_router){ block_scope = self } + assert_equal new_router, subject.router + assert_equal new_router, block_scope + end + + should "allow setting the configuration template source" do + new_template_source = Factory.string + subject.template_source(new_template_source) + assert_equal new_template_source, subject.configuration.template_source + assert_equal new_template_source, subject.template_source + end + + should "allow setting its template source with a path and block" do + new_path = Factory.string + yielded = nil + subject.build_template_source(new_path){ |s| yielded = s } + assert_equal new_path, subject.configuration.template_source.path + assert_equal subject.configuration.template_source, yielded + end + + should "allow setting its template source with only a path" do + new_path = Factory.string + subject.build_template_source(new_path) + assert_equal new_path, subject.configuration.template_source.path + end + + end + + class InitTests < UnitTests + desc "when init" + setup do + @server_class.name Factory.string + @server_class.ip Factory.string + @server_class.port Factory.integer + @server_class.error{ Factory.string } + @server_class.router do + service Factory.string, TestHandler.to_s + end + + @dat_tcp_server_spy = DatTCP::ServerSpy.new + Assert.stub(DatTCP::Server, :new) do |&block| + @dat_tcp_server_spy.serve_proc = block + @dat_tcp_server_spy + end + + @server = @server_class.new + end subject{ @server } - should have_readers :sanford_host, :sanford_host_data, :sanford_host_options - should have_imeths :on_run + should have_readers :server_data, :dat_tcp_server + should have_imeths :name, :ip, :port + should have_imeths :file_descriptor, :client_file_descriptors + should have_imeths :listen, :start, :pause, :stop, :halt + should have_imeths :paused? - should "include DatTCP::Server" do - assert_includes DatTCP::Server, subject.class + should "have validated its configuration" do + assert_true subject.class.configuration.valid? end - should "save its host and host options but not initialize a host data yet" do - assert_equal TestHost, subject.sanford_host - assert_equal true, subject.sanford_host_options[:receives_keep_alive] - assert_nil subject.sanford_host_data + should "know its server data" do + configuration = subject.class.configuration + sd = subject.server_data + + assert_instance_of Sanford::ServerData, sd + assert_equal configuration.name, sd.name + assert_equal configuration.ip, sd.ip + assert_equal configuration.port, sd.port + assert_equal configuration.verbose_logging, sd.verbose_logging + assert_equal configuration.receives_keep_alive, sd.receives_keep_alive + assert_equal configuration.error_procs, sd.error_procs + assert_equal configuration.routes, sd.routes.values + assert_instance_of configuration.logger.class, sd.logger end + should "know its dat tcp server" do + assert_equal @dat_tcp_server_spy, subject.dat_tcp_server + assert_not_nil @dat_tcp_server_spy.serve_proc + end + + should "know its name, pid file and logger" do + assert_equal subject.server_data.name, subject.name + assert_equal subject.server_data.pid_file, subject.pid_file + assert_equal subject.server_data.logger, subject.logger + end + + should "call listen on its dat tcp server using `listen`" do + subject.listen + assert_true @dat_tcp_server_spy.listen_called + end + + should "use its configured ip and port by default when listening" do + subject.listen + assert_equal subject.server_data.ip, @dat_tcp_server_spy.ip + assert_equal subject.server_data.port, @dat_tcp_server_spy.port + end + + should "pass any args to its dat tcp server using `listen`" do + ip, port = Factory.string, Factory.integer + subject.listen(ip, port) + assert_equal ip, @dat_tcp_server_spy.ip + assert_equal port, @dat_tcp_server_spy.port + + file_descriptor = Factory.integer + subject.listen(file_descriptor) + assert_equal file_descriptor, @dat_tcp_server_spy.file_descriptor + end + + should "know its ip, port and file descriptor" do + assert_equal @dat_tcp_server_spy.ip, subject.ip + assert_equal @dat_tcp_server_spy.port, subject.port + subject.listen + assert_equal @dat_tcp_server_spy.ip, subject.ip + assert_equal @dat_tcp_server_spy.port, subject.port + + assert_equal @dat_tcp_server_spy.file_descriptor, subject.file_descriptor + subject.listen(Factory.integer) + assert_equal @dat_tcp_server_spy.file_descriptor, subject.file_descriptor + end + + should "call start on its dat tcp server using `start`" do + client_fds = [ Factory.integer ] + subject.start(client_fds) + assert_true @dat_tcp_server_spy.start_called + assert_equal client_fds, @dat_tcp_server_spy.client_file_descriptors + end + + should "know its client file descriptors" do + expected = @dat_tcp_server_spy.client_file_descriptors + assert_equal expected, subject.client_file_descriptors + subject.start([ Factory.integer ]) + expected = @dat_tcp_server_spy.client_file_descriptors + assert_equal expected, subject.client_file_descriptors + end + + should "call pause on its dat tcp server using `pause`" do + wait = Factory.boolean + subject.pause(wait) + assert_true @dat_tcp_server_spy.pause_called + assert_equal wait, @dat_tcp_server_spy.waiting_for_pause + end + + should "call stop on its dat tcp server using `stop`" do + wait = Factory.boolean + subject.stop(wait) + assert_true @dat_tcp_server_spy.stop_called + assert_equal wait, @dat_tcp_server_spy.waiting_for_stop + end + + should "call halt on its dat tcp server using `halt`" do + wait = Factory.boolean + subject.halt(wait) + assert_true @dat_tcp_server_spy.halt_called + assert_equal wait, @dat_tcp_server_spy.waiting_for_halt + end + + should "know if its been paused" do + assert_false subject.paused? + subject.listen + assert_true subject.paused? + subject.start + assert_false subject.paused? + subject.pause + assert_true subject.paused? + end + end - class RunTests < UnitTests - desc "run" + class ConfigureTCPServerTests < InitTests + desc "configuring its tcp server" setup do - @server.listen(TestHost.ip, TestHost.port) - @server.run + @tcp_server = TCPServerSpy.new + Assert.stub(@dat_tcp_server_spy, :listen) do |*args, &block| + @configure_tcp_server_proc = block + end + @server.listen + @configure_tcp_server_proc.call(@tcp_server) end - teardown do - @server.stop + subject{ @tcp_server } + + should "set the TCP_NODELAY option" do + expected = [ ::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true ] + assert_includes expected, @tcp_server.set_socket_option_calls end - should "have initialized a host data instance" do - assert_instance_of Sanford::HostData, subject.sanford_host_data + end + + class ServeTests < InitTests + desc "serve" + setup do + @socket = Factory.binary + + @connection = FakeServerConnection.new + Assert.stub(Connection, :new).with(@socket){ @connection } + + @connection_handler_spy = ConnectionHandlerSpy.new + Assert.stub(Sanford::ConnectionHandler, :new).tap do |s| + s.with(@server.server_data, @connection){ @connection_handler_spy } + end + + @serve_proc = @dat_tcp_server_spy.serve_proc end + subject{ @serve_proc } + should "run a connection_handler when called with a socket" do + Assert.stub(@server.server_data, :receives_keep_alive){ false } + @connection.read_data = Factory.boolean + assert_false @connection_handler_spy.run_called + subject.call(@socket) + assert_true @connection_handler_spy.run_called + end + + should "not run a keep-alive connection when configured to receive them" do + Assert.stub(@server.server_data, :receives_keep_alive){ true } + @connection.read_data = nil # nothing to read makes it a keep-alive + assert_false @connection_handler_spy.run_called + subject.call(@socket) + assert_false @connection_handler_spy.run_called + end + + should "run a keep-alive connection when configured to receive them" do + Assert.stub(@server.server_data, :receives_keep_alive){ false } + @connection.read_data = nil # nothing to read makes it a keep-alive + assert_false @connection_handler_spy.run_called + subject.call(@socket) + assert_true @connection_handler_spy.run_called + end + end - # Sanford::Server#serve is tested in test/system/request_handling_test.rb, - # it requires multiple parts of Sanford and basically tests a large portion of - # the entire system + class ConnectionTests < UnitTests + desc "Connection" + setup do + fake_socket = Factory.string + @protocol_conn = Sanford::Protocol::FakeConnection.new(Factory.binary) + Assert.stub(Sanford::Protocol::Connection, :new).with(fake_socket) do + @protocol_conn + end + @connection = Connection.new(fake_socket) + end + subject{ @connection } + + should have_imeths :read_data, :write_data, :peek_data + should have_imeths :close_write + + should "default its timeout" do + assert_equal 1.0, subject.timeout + end + + should "allowing reading from the protocol connection" do + result = subject.read_data + assert_equal @protocol_conn.read_data, result + assert_equal @protocol_conn.read_timeout, subject.timeout + end + + should "allowing writing to the protocol connection" do + data = Factory.binary + subject.write_data(data) + assert_equal @protocol_conn.write_data, data + end + + should "allowing peeking from the protocol connection" do + result = subject.peek_data + assert_equal @protocol_conn.peek_data, result + assert_equal @protocol_conn.peek_timeout, subject.timeout + end + + should "allow closing the write stream on the protocol connection" do + assert_false @protocol_conn.closed_write + subject.close_write + assert_true @protocol_conn.closed_write + end + + end + + class TCPCorkTests < UnitTests + desc "TCPCork" + subject{ TCPCork } + + should have_imeths :apply, :remove + + end + + class ConfigurationTests < UnitTests + include NsOptions::AssertMacros + + desc "Configuration" + setup do + @configuration = Configuration.new.tap do |c| + c.name Factory.string + c.ip Factory.string + c.port Factory.integer + end + end + subject{ @configuration } + + should have_options :name, :ip, :port, :pid_file + should have_options :receives_keep_alive + should have_options :verbose_logging, :logger + should have_options :template_source + should have_accessors :init_procs, :error_procs + should have_accessors :router + should have_imeths :routes + should have_imeths :to_hash + should have_imeths :valid?, :validate! + + should "be an ns-options proxy" do + assert_includes NsOptions::Proxy, subject.class + end + + should "default its options" do + config = Configuration.new + assert_nil config.name + assert_equal '0.0.0.0', config.ip + assert_nil config.port + assert_nil config.pid_file + + assert_false config.receives_keep_alive + + assert_true config.verbose_logging + assert_instance_of Sanford::NullLogger, config.logger + assert_instance_of Sanford::NullTemplateSource, config.template_source + + assert_equal [], config.init_procs + assert_equal [], config.error_procs + + assert_instance_of Sanford::Router, config.router + assert_empty config.router.routes + end + + should "not be valid by default" do + assert_false subject.valid? + end + + should "know its routes" do + assert_equal subject.router.routes, subject.routes + subject.router.service(Factory.string, TestHandler.to_s) + assert_equal subject.router.routes, subject.routes + end + + should "include its routes, error procs and template source in its hash" do + config_hash = subject.to_hash + assert_equal subject.error_procs, config_hash[:error_procs] + assert_equal subject.routes, config_hash[:routes] + template_source = subject.template_source + assert_instance_of template_source.class, config_hash[:template_source] + assert_equal template_source.path, config_hash[:template_source].path + end + + should "call its init procs when validated" do + called = false + subject.init_procs << proc{ called = true } + subject.validate! + assert_true called + end + + should "ensure its required options have been set when validated" do + subject.name = nil + assert_raises(InvalidError){ subject.validate! } + subject.name = Factory.string + + subject.ip = nil + assert_raises(InvalidError){ subject.validate! } + subject.ip = Factory.string + + subject.port = nil + assert_raises(InvalidError){ subject.validate! } + subject.port = Factory.integer + + assert_nothing_raised{ subject.validate! } + end + + should "validate its routes when validated" do + subject.router.service(Factory.string, TestHandler.to_s) + subject.routes.each{ |route| assert_nil route.handler_class } + subject.validate! + subject.routes.each{ |route| assert_not_nil route.handler_class } + end + + should "be valid after being validated" do + assert_false subject.valid? + subject.validate! + assert_true subject.valid? + end + + should "only be able to be validated once" do + called = 0 + subject.init_procs << proc{ called += 1 } + subject.validate! + assert_equal 1, called + subject.validate! + assert_equal 1, called + end + + end + + TestHandler = Class.new + + class TCPServerSpy + attr_reader :set_socket_option_calls + + def initialize + @set_socket_option_calls = [] + end + + def setsockopt(*args) + @set_socket_option_calls << args + end + end + + class ConnectionHandlerSpy + attr_reader :run_called + + def initialize + @run_called = false + end + + def run + @run_called = true + end + end end