# Specs for UNIX sockets $:.push File.expand_path(File.dirname(__FILE__)) $:.push File.expand_path("#{File.dirname(__FILE__)}/../..") $:.push File.expand_path("#{File.dirname(__FILE__)}/../../lib") # Project require 'config/default' begin require "config/local" rescue LoadError # Nothing to do, this can fail end topic "rubinius::mod::UnixSocket" do include "" include "" include "" include "" include "" # Mocks, system function "overrides" for verification include "spec_helper.hpp" include "unix_socket_spec_helper.hpp" # The build intentionally substitutes the root for # the normal include dir so that we can inject the # spec code unobtrusively. include "include/mod_rubinius/unix_socket.hpp" header %{ using rubinius::mod::UnixSocket; using rubinius::mod::SocketError; using rubinius::mod::SystemError; using rubinius::mod::FilesystemError; using rubinius::mod::NetworkError; using rubinius::mod::stream::eos; using rubinius::mod::stream::next; } # Actual specs here describe "creating a new socket" do # These are ugly because we cannot use contexts yet. before_each %{ SocketSequence::next_return_sequence_is(101); ConnectSequence::next_return_sequence_is(0); ShutdownSequence::next_return_sequence_is(ShutdownReturner(0)); return NULL; // No context } it "takes a socket file name as constructor argument", %{ UnixSocket sock("#{$conf.spec_socketfile}"); specify(true); // Getting here means success } it "allows setting a timeout for operations on this socket defaulting to 10s", %{ } it "creates unix domain socket descriptor as part of initialisation", %{ UnixSocket sock("#{$conf.spec_socketfile}"); const SocketSequence::ArgsType& received = SocketSequence::received().front(); specify(received.domain, should.equal(AF_LOCAL)); specify(received.type, should.equal(SOCK_STREAM)); specify(received.protocol, should.equal(0)); } it "raises exception with explanation if system is unable to create a socket", %| SocketSequence::next_return_sequence_is(-1); bool raised = false; try { UnixSocket sock("this string is not used here"); } catch (const SystemError& ex) { raised = !ex.message().empty(); } specify(raised, should.equal(true)); | it "immediately connects descriptor to named socket", %{ UnixSocket sock("#{$conf.spec_socketfile}"); const ConnectSequence::ArgsType& received = ConnectSequence::received().front(); const struct sockaddr_un* address = reinterpret_cast(received.address); specify(received.fd, should.equal(101)); specify(std::string("#{$conf.spec_socketfile}") == address->sun_path); specify(received.address_size == SUN_LEN(address)); } # The exact value is platform-dependent so we lean on that it "raises exception if socket file path given is too long", %| std::string error; try { UnixSocket sock(std::string(UnixSocket::LONGEST_ALLOWED_PATH + 1, 'a')); } catch (const FilesystemError& ex) { error = ex.message(); } specify(error, should.match(".*too.*long.*")); // Fast and loose | # This is intentionally a bit vague it "raises exception with an explanation if path not valid or permission issue or other system issue occurs", %| ConnectSequence::next_return_sequence_is(-1); bool raised = false; try { UnixSocket sock("this string is not used here"); } catch (const std::exception& ex) { raised = std::strlen(ex.what()) != 0; } specify(raised, should.equal(true)); | end describe "newly created socket" do before_each %{ SocketSequence::next_return_sequence_is(102); ConnectSequence::next_return_sequence_is(0); ShutdownSequence::next_return_sequence_is(ShutdownReturner(0)); return new UnixSocket("#{$conf.spec_socketfile}"); } it "is not closed", %{ specify(not should.be().closed_P()); } it "is not in end of file state", %{ specify(not should.be().eof_P()); } end # ... #ifndef __MOD_RUBINIUS_UNIX_SOCKET_SPEC_HPP__ #define __MOD_RUBINIUS_UNIX_SOCKET_SPEC_HPP__ 1 #include "CppSpec.h" #include #include #include #include #include #include "spec_helper.hpp" #include "unix_socket_spec_helper.hpp" #include "include/mod_rubinius/unix_socket.hpp" using rubinius::mod::UnixSocket; using rubinius::mod::SocketError; using rubinius::mod::SystemError; using rubinius::mod::FilesystemError; using rubinius::mod::NetworkError; using rubinius::mod::stream::eos; using rubinius::mod::stream::next; class creating_a_new_socket : public CppSpec::Specification< rubinius::mod::UnixSocket > { public: creating_a_new_socket() { REGISTER_BEHAVIOUR(creating_a_new_socket, takes_a_socket_file_name_as_constructor_argument); REGISTER_BEHAVIOUR(creating_a_new_socket, allows_setting_a_timeout_for_operations_on_this_socket_defaulting_to_10s); REGISTER_BEHAVIOUR(creating_a_new_socket, creates_unix_domain_socket_descriptor_as_part_of_initialisation); REGISTER_BEHAVIOUR(creating_a_new_socket, raises_exception_with_explanation_if_system_is_unable_to_create_a_socket); REGISTER_BEHAVIOUR(creating_a_new_socket, immediately_connects_descriptor_to_named_socket); REGISTER_BEHAVIOUR(creating_a_new_socket, raises_exception_if_socket_file_path_given_is_too_long); REGISTER_BEHAVIOUR(creating_a_new_socket, raises_exception_with_an_explanation_if_path_not_valid_or_permission_issue_or_other_system_issue_occurs); } rubinius::mod::UnixSocket* createContext() { SocketSequence::next_return_sequence_is(101); ConnectSequence::next_return_sequence_is(0); ShutdownSequence::next_return_sequence_is(ShutdownReturner(0)); return NULL; // No context } public: void takes_a_socket_file_name_as_constructor_argument() { UnixSocket sock("/tmp/mod_rbx_testing.sock"); specify(true); // Getting here means success } void allows_setting_a_timeout_for_operations_on_this_socket_defaulting_to_10s() { specify(false); } void creates_unix_domain_socket_descriptor_as_part_of_initialisation() { UnixSocket sock("/tmp/mod_rbx_testing.sock"); const SocketSequence::ArgsType& received = SocketSequence::received().front(); specify(received.domain, should.equal(AF_LOCAL)); specify(received.type, should.equal(SOCK_STREAM)); specify(received.protocol, should.equal(0)); } void raises_exception_with_explanation_if_system_is_unable_to_create_a_socket() { SocketSequence::next_return_sequence_is(-1); bool raised = false; try { UnixSocket sock("this string is not used here"); } catch (const SystemError& ex) { raised = !ex.message().empty(); } specify(raised, should.equal(true)); } void immediately_connects_descriptor_to_named_socket() { UnixSocket sock("/tmp/mod_rbx_testing.sock"); const ConnectSequence::ArgsType& received = ConnectSequence::received().front(); const struct sockaddr_un* address = reinterpret_cast(received.address); specify(received.fd, should.equal(101)); specify(std::string("/tmp/mod_rbx_testing.sock") == address->sun_path); specify(received.address_size == SUN_LEN(address)); } void raises_exception_if_socket_file_path_given_is_too_long() { std::string error; try { UnixSocket sock(std::string(UnixSocket::LONGEST_ALLOWED_PATH + 1, 'a')); } catch (const FilesystemError& ex) { error = ex.message(); } specify(error, should.match(".*too.*long.*")); // Fast and loose } void raises_exception_with_an_explanation_if_path_not_valid_or_permission_issue_or_other_system_issue_occurs() { ConnectSequence::next_return_sequence_is(-1); bool raised = false; try { UnixSocket sock("this string is not used here"); } catch (const std::exception& ex) { raised = std::strlen(ex.what()) != 0; } specify(raised, should.equal(true)); } }; REGISTER_SPECIFICATION(creating_a_new_socket)