# 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 "<string>"
include "<exception>"
include "<stdexcept>"
include "<cerrno>"
include "<cstring>"
# 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<const struct sockaddr_un*>(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 <string>
#include <exception>
#include <stdexcept>
#include <cerrno>
#include <cstring>
#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<const struct sockaddr_un*>(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)