Merge branch 'fastbinary'
git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@674688 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/configure.ac b/configure.ac
index df5c229..ef163f7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -61,6 +61,12 @@
fi
AM_CONDITIONAL(WITH_PYTHON, [test -n "$PYTHON" -a "$PYTHON" != ":"])
+AX_THRIFT_LIB(ruby, [Ruby], yes)
+if test "$with_ruby" = "yes"; then
+ AC_PATH_PROG([RUBY], [ruby])
+fi
+AM_CONDITIONAL(ENABLE_RUBY, [test -n "$RUBY"])
+
AC_C_CONST
AC_C_INLINE
AC_C_VOLATILE
@@ -165,10 +171,12 @@
lib/csharp/Makefile
lib/java/Makefile
lib/py/Makefile
+ lib/rb/Makefile
if/Makefile
test/Makefile
test/py/Makefile
test/java/Makefile
+ test/rb/Makefile
])
AC_OUTPUT
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 09236ce..0765f03 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -17,12 +17,14 @@
SUBDIRS += erl
endif
+if ENABLE_RUBY
+SUBDIRS += rb
+endif
+
EXTRA_DIST = \
cocoa \
hs \
ocaml \
perl \
php \
- rb \
- erl \
st
diff --git a/lib/rb/Makefile.am b/lib/rb/Makefile.am
new file mode 100644
index 0000000..43acfff
--- /dev/null
+++ b/lib/rb/Makefile.am
@@ -0,0 +1,13 @@
+EXTRA_DIST = setup.rb lib ext
+
+all-local:
+ $(RUBY) setup.rb config
+ $(RUBY) setup.rb setup
+
+install-exec-hook:
+ $(RUBY) setup.rb install
+
+clean-local:
+ $(RUBY) setup.rb clean
+
+check-local: all
diff --git a/lib/rb/Manifest b/lib/rb/Manifest
index b90a86d..1b408fc 100644
--- a/lib/rb/Manifest
+++ b/lib/rb/Manifest
@@ -8,11 +8,14 @@
benchmark/thin_server.rb
CHANGELOG
COPYING
+ext/binaryprotocolaccelerated.c
+ext/extconf.rb
lib/thrift/client.rb
lib/thrift/deprecation.rb
lib/thrift/exceptions.rb
lib/thrift/processor.rb
lib/thrift/protocol/binaryprotocol.rb
+lib/thrift/protocol/binaryprotocolaccelerated.rb
lib/thrift/protocol/tbinaryprotocol.rb
lib/thrift/protocol/tprotocol.rb
lib/thrift/protocol.rb
@@ -34,9 +37,13 @@
lib/thrift/types.rb
lib/thrift.rb
LICENSE
+Makefile
+Makefile.am
+Makefile.in
Manifest
Rakefile
README
+setup.rb
spec/backwards_compatibility_spec.rb
spec/binaryprotocol_spec.rb
spec/client_spec.rb
diff --git a/lib/rb/Rakefile b/lib/rb/Rakefile
index e88e984..6ddca52 100644
--- a/lib/rb/Rakefile
+++ b/lib/rb/Rakefile
@@ -23,7 +23,7 @@
# ensure this is a full thrift checkout and not a tarball of the ruby libs
cmd = 'head -1 ../../README 2>/dev/null | grep Thrift >/dev/null 2>/dev/null'
system(cmd) or fail "rake test requires a full thrift checkout"
- sh 'make', '-C', File.dirname(__FILE__) + "/../../test/rb"
+ sh 'make', '-C', File.dirname(__FILE__) + "/../../test/rb", "check"
end
desc 'Compile the .thrift files for the specs'
@@ -57,6 +57,15 @@
p.url = "http://incubator.apache.org/thrift/"
p.include_rakefile = true
end
+
+ task :install => [:check_site_lib]
+
+ require 'rbconfig'
+ task :check_site_lib do
+ if File.exist?(File.join(Config::CONFIG['sitelibdir'], 'thrift.rb'))
+ fail "thrift is already installed in site_ruby"
+ end
+ end
rescue LoadError
[:install, :package].each do |t|
desc "Stub for #{t}"
diff --git a/lib/rb/ext/binaryprotocolaccelerated.c b/lib/rb/ext/binaryprotocolaccelerated.c
new file mode 100644
index 0000000..2725bfc
--- /dev/null
+++ b/lib/rb/ext/binaryprotocolaccelerated.c
@@ -0,0 +1,1231 @@
+// Half of this file comes from contributions from Nitay Joffe (nitay@powerset.com)
+// Much of the rest (almost) directly ported (or pulled) from thrift-py's fastbinary.c
+// Everything else via Kevin Clark (kevin@powerset.com)
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <ruby.h>
+#include <st.h>
+#include <netinet/in.h>
+
+// #define __DEBUG__
+
+#ifndef HAVE_STRLCPY
+
+static
+size_t
+strlcpy (char *dst, const char *src, size_t dst_sz)
+{
+ size_t n;
+
+ for (n = 0; n < dst_sz; n++) {
+ if ((*dst++ = *src++) == '\0')
+ break;
+ }
+
+ if (n < dst_sz)
+ return n;
+ if (n > 0)
+ *(dst - 1) = '\0';
+ return n + strlen (src);
+}
+
+#endif
+
+#define dbg() fprintf(stderr, "%s:%d\n", __FUNCTION__, __LINE__)
+
+
+// TODO (kevinclark): This was here from the patch/python. Not sure
+// If it's actually that big a pain. Need to look into pulling
+// From the right place
+
+// Stolen out of TProtocol.h.
+// It would be a huge pain to have both get this from one place.
+
+enum TType {
+ T_STOP = 0,
+ T_BOOL = 2,
+ T_BYTE = 3,
+ T_I16 = 6,
+ T_I32 = 8,
+ T_I64 = 10,
+ T_DBL = 4,
+ T_STR = 11,
+ T_STRCT = 12,
+ T_MAP = 13,
+ T_SET = 14,
+ T_LIST = 15
+ // T_VOID = 1,
+ // T_I08 = 3,
+ // T_U64 = 9,
+ // T_UTF7 = 11,
+ // T_UTF8 = 16,
+ // T_UTF16 = 17
+};
+
+#define IS_CONTAINER(x) (x == T_MAP || x == T_SET || x == T_LIST)
+
+// Same comment as the enum. Sorry.
+#ifdef HAVE_ENDIAN_H
+#include <endian.h>
+#endif
+
+#ifndef __BYTE_ORDER
+# if defined(BYTE_ORDER) && defined(LITTLE_ENDIAN) && defined(BIG_ENDIAN)
+# define __BYTE_ORDER BYTE_ORDER
+# define __LITTLE_ENDIAN LITTLE_ENDIAN
+# define __BIG_ENDIAN BIG_ENDIAN
+# else
+# error "Cannot determine endianness"
+# endif
+#endif
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+# define ntohll(n) (n)
+# define htonll(n) (n)
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+# if defined(__GNUC__) && defined(__GLIBC__)
+# include <byteswap.h>
+# define ntohll(n) bswap_64(n)
+# define htonll(n) bswap_64(n)
+# else /* GNUC & GLIBC */
+# define ntohll(n) ( (((unsigned long long)ntohl(n)) << 32) + ntohl(n >> 32) )
+# define htonll(n) ( (((unsigned long long)htonl(n)) << 32) + htonl(n >> 32) )
+# endif /* GNUC & GLIBC */
+#else /* __BYTE_ORDER */
+# error "Can't define htonll or ntohll!"
+#endif
+
+
+// -----------------------------------------------------------------------------
+// Cached interned strings and such
+// -----------------------------------------------------------------------------
+
+static VALUE class_tbpa;
+static VALUE m_thrift;
+static VALUE rb_cSet;
+static ID type_sym;
+static ID class_sym;
+static ID key_sym;
+static ID value_sym;
+static ID element_sym;
+static ID name_sym;
+static ID fields_id;
+static ID consume_bang_id;
+static ID string_buffer_id;
+static ID borrow_id;
+static ID keys_id;
+static ID entries_id;
+
+static const uint32_t VERSION_MASK = 0xffff0000;
+static const uint32_t VERSION_1 = 0x80010000;
+
+// -----------------------------------------------------------------------------
+// Structs so I don't have to keep calling rb_hash_aref
+// -----------------------------------------------------------------------------
+
+// { :type => field[:type],
+// :class => field[:class],
+// :key => field[:key],
+// :value => field[:value],
+// :element => field[:element] }
+
+struct _thrift_map;
+struct _field_spec;
+
+typedef union {
+ VALUE class;
+ struct _thrift_map* map;
+ struct _field_spec* element;
+} container_data;
+
+typedef struct _field_spec {
+ int type;
+ char* name;
+ container_data data;
+} field_spec;
+
+typedef struct _thrift_map {
+ field_spec* key;
+ field_spec* value;
+} thrift_map;
+
+
+static void free_field_spec(field_spec* spec) {
+ switch(spec->type) {
+ case T_LIST:
+ case T_SET:
+ free_field_spec(spec->data.element);
+ break;
+
+ case T_MAP:
+ free_field_spec(spec->data.map->key);
+ free_field_spec(spec->data.map->value);
+ free(spec->data.map);
+ break;
+ }
+
+ free(spec);
+}
+
+// Parses a ruby field spec into a C struct
+//
+// Simple fields look like:
+// { :name => .., :type => .. }
+// Structs add the :class attribute
+// Maps adds :key and :value attributes, field specs
+// Lists and Sets add an :element, a field spec
+static field_spec* parse_field_spec(VALUE field_data) {
+ int type = NUM2INT(rb_hash_aref(field_data, type_sym));
+ VALUE name = rb_hash_aref(field_data, name_sym);
+ field_spec* spec = (field_spec *) malloc(sizeof(field_spec));
+
+#ifdef __DEBUG__ // No need for this in prod since I set all the fields
+ bzero(spec, sizeof(field_spec));
+#endif
+
+ spec->type = type;
+
+ if (Qnil != name) {
+ spec->name = StringValuePtr(name);
+ } else {
+ spec->name = NULL;
+ }
+
+ switch(type) {
+ case T_STRCT: {
+ spec->data.class = rb_hash_aref(field_data, class_sym);
+ break;
+ }
+
+ case T_MAP: {
+ VALUE key_fields = rb_hash_aref(field_data, key_sym);
+ VALUE value_fields = rb_hash_aref(field_data, value_sym);
+ thrift_map* map = (thrift_map *) malloc(sizeof(thrift_map));
+
+ map->key = parse_field_spec(key_fields);
+ map->value = parse_field_spec(value_fields);
+ spec->data.map = map;
+
+ break;
+ }
+
+ case T_LIST:
+ case T_SET:
+ {
+ VALUE list_fields = rb_hash_aref(field_data, element_sym);
+ spec->data.element = parse_field_spec(list_fields);
+ break;
+ }
+ }
+
+ return spec;
+}
+
+
+// -----------------------------------------------------------------------------
+// Serialization routines
+// -----------------------------------------------------------------------------
+
+
+// write_*(VALUE buf, ...) takes a value and adds it to a Ruby string buffer,
+// in network order
+static void write_byte(VALUE buf, int8_t val) {
+ rb_str_buf_cat(buf, (char*)&val, sizeof(int8_t));
+}
+
+static void write_i16(VALUE buf, int16_t val) {
+ int16_t net = (int16_t)htons(val);
+ rb_str_buf_cat(buf, (char*)&net, sizeof(int16_t));
+}
+
+static void write_i32(VALUE buf, int32_t val) {
+ int32_t net = (int32_t)htonl(val);
+ rb_str_buf_cat(buf, (char*)&net, sizeof(int32_t));
+}
+
+static void write_i64(VALUE buf, int64_t val) {
+ int64_t net = (int64_t)htonll(val);
+ rb_str_buf_cat(buf, (char*)&net, sizeof(int64_t));
+}
+
+static void write_double(VALUE buf, double dub) {
+ // Unfortunately, bitwise_cast doesn't work in C. Bad C!
+ union {
+ double f;
+ int64_t t;
+ } transfer;
+ transfer.f = dub;
+ write_i64(buf, transfer.t);
+}
+
+static void write_string(VALUE buf, char* str) {
+ int32_t len = strlen(str);
+ write_i32(buf, len);
+ rb_str_buf_cat2(buf, str);
+}
+
+// Some functions macro'd out because they're nops for the binary protocol
+// but placeholders were desired in case things change
+#define write_struct_begin(buf)
+#define write_struct_end(buf)
+
+static void write_field_begin(VALUE buf, char* name, int type, int fid) {
+#ifdef __DEBUG__
+ fprintf(stderr, "Writing field beginning: %s %d %d\n", name, type, fid);
+#endif
+
+ write_byte(buf, (int8_t)type);
+ write_i16(buf, (int16_t)fid);
+}
+
+#define write_field_end(buf)
+
+static void write_field_stop(VALUE buf) {
+ write_byte(buf, T_STOP);
+}
+
+static void write_map_begin(VALUE buf, int8_t ktype, int8_t vtype, int32_t sz) {
+ write_byte(buf, ktype);
+ write_byte(buf, vtype);
+ write_i32(buf, sz);
+}
+
+#define write_map_end(buf);
+
+static void write_list_begin(VALUE buf, int type, int sz) {
+ write_byte(buf, type);
+ write_i32(buf, sz);
+}
+
+#define write_list_end(buf)
+
+static void write_set_begin(VALUE buf, int type, int sz) {
+ write_byte(buf, type);
+ write_i32(buf, sz);
+}
+
+#define write_set_end(buf)
+
+static void binary_encoding(VALUE buf, VALUE obj, int type);
+
+// Handles container types: Map, Set, List
+static void write_container(VALUE buf, VALUE value, field_spec* spec) {
+ int sz, i;
+
+ switch(spec->type) {
+ case T_MAP: {
+ VALUE keys;
+ VALUE key;
+ VALUE val;
+
+ keys = rb_funcall(value, keys_id, 0);
+
+ sz = RARRAY(keys)->len;
+
+ write_map_begin(buf, spec->data.map->key->type, spec->data.map->value->type, sz);
+
+ for (i = 0; i < sz; i++) {
+ key = rb_ary_entry(keys, i);
+ val = rb_hash_aref(value, key);
+
+ if (IS_CONTAINER(spec->data.map->key->type)) {
+ write_container(buf, key, spec->data.map->key);
+ } else {
+ binary_encoding(buf, key, spec->data.map->key->type);
+ }
+
+ if (IS_CONTAINER(spec->data.map->value->type)) {
+ write_container(buf, val, spec->data.map->value);
+ } else {
+ binary_encoding(buf, val, spec->data.map->value->type);
+ }
+ }
+
+ write_map_end(buf);
+
+ break;
+ }
+
+ case T_LIST: {
+ sz = RARRAY(value)->len;
+
+ write_list_begin(buf, spec->data.element->type, sz);
+ for (i = 0; i < sz; ++i) {
+ if (IS_CONTAINER(spec->data.element->type)) {
+ write_container(buf, rb_ary_entry(value, i), spec->data.element);
+ } else {
+ binary_encoding(buf, rb_ary_entry(value, i), spec->data.element->type);
+ }
+ }
+ write_list_end(buf);
+ break;
+ }
+
+ case T_SET: {
+ VALUE items;
+
+ if (TYPE(value) == T_ARRAY) {
+ items = value;
+ } else {
+ if (rb_cSet == CLASS_OF(value)) {
+ items = rb_funcall(value, entries_id, 0);
+ } else {
+ Check_Type(value, T_HASH);
+ items = rb_funcall(value, keys_id, 0);
+ }
+ }
+
+ sz = RARRAY(items)->len;
+
+ write_set_begin(buf, spec->data.element->type, sz);
+
+ for (i = 0; i < sz; i++) {
+ if (IS_CONTAINER(spec->data.element->type)) {
+ write_container(buf, rb_ary_entry(items, i), spec->data.element);
+ } else {
+ binary_encoding(buf, rb_ary_entry(items, i), spec->data.element->type);
+ }
+ }
+
+ write_set_end(buf);
+ break;
+ }
+ }
+}
+
+// Takes the field id, data to be encoded, buffer and enclosing object
+// to be encoded. buf and obj passed as a ruby array for rb_hash_foreach.
+// TODO(kevinclark): See if they can be passed individually to avoid object
+// creation
+static int encode_field(VALUE fid, VALUE data, VALUE ary) {
+ field_spec *spec = parse_field_spec(data);
+
+ VALUE buf = rb_ary_entry(ary, 0);
+ VALUE obj = rb_ary_entry(ary, 1);
+ char name_buf[128];
+
+ name_buf[0] = '@';
+ strlcpy(&name_buf[1], spec->name, sizeof(name_buf) - 1);
+
+ VALUE value = rb_ivar_get(obj, rb_intern(name_buf));
+
+ if (Qnil == value) {
+ free_field_spec(spec);
+ return 0;
+ }
+
+ write_field_begin(buf, spec->name, spec->type, NUM2INT(fid));
+
+ if (IS_CONTAINER(spec->type)) {
+ write_container(buf, value, spec);
+ } else {
+ binary_encoding(buf, value, spec->type);
+ }
+ write_field_end(buf);
+
+ free_field_spec(spec);
+
+ return 0;
+}
+
+// -----------------------------------------------------------------------------
+// TFastBinaryProtocol's main encoding loop
+// -----------------------------------------------------------------------------
+
+static void binary_encoding(VALUE buf, VALUE obj, int type) {
+#ifdef __DEBUG__
+ rb_p(rb_str_new2("Encoding binary (buf, obj, type)"));
+ rb_p(rb_inspect(buf));
+ rb_p(rb_inspect(obj));
+ rb_p(rb_inspect(INT2FIX(type)));
+#endif
+
+ switch(type) {
+ case T_BOOL:
+ if RTEST(obj) {
+ write_byte(buf, 1);
+ }
+ else {
+ write_byte(buf, 0);
+ }
+
+ break;
+
+ case T_BYTE:
+ write_byte(buf, NUM2INT(obj));
+ break;
+
+ case T_I16:
+ write_i16(buf, NUM2INT(obj));
+ break;
+
+ case T_I32:
+ write_i32(buf, NUM2INT(obj));
+ break;
+
+ case T_I64:
+ write_i64(buf, rb_num2ll(obj));
+ break;
+
+ case T_DBL:
+ write_double(buf, NUM2DBL(obj));
+ break;
+
+ case T_STR:
+ write_string(buf, StringValuePtr(obj));
+ break;
+
+ case T_STRCT: {
+ // rb_hash_foreach has to take args as a ruby array
+ VALUE args = rb_ary_new3(2, buf, obj);
+ VALUE fields = rb_const_get(CLASS_OF(obj), fields_id);
+
+ write_struct_begin(buf);
+
+ rb_hash_foreach(fields, encode_field, args);
+
+ write_field_stop(buf);
+ write_struct_end(buf);
+ break;
+ }
+
+ default: {
+ rb_raise(rb_eNotImpError, "Unknown type for binary_encoding: %d", type);
+ }
+ }
+}
+
+// obj is always going to be a TSTRCT
+static VALUE tbpa_encode_binary(VALUE self, VALUE obj) {
+ VALUE buf = rb_str_buf_new(1024);
+ binary_encoding(buf, obj, T_STRCT);
+ return buf;
+}
+
+
+// -----------------------------------------------------------------------------
+// Read stuff
+// -----------------------------------------------------------------------------
+
+typedef struct {
+ char* name;
+ int8_t type;
+ int16_t id;
+} field_header;
+
+typedef struct {
+ int8_t key_type;
+ int8_t val_type;
+ int num_entries;
+} map_header;
+
+typedef struct {
+ int8_t type;
+ int num_elements;
+} list_header;
+
+typedef list_header set_header;
+
+typedef struct {
+ char* data;
+ int pos;
+ int len;
+ VALUE trans;
+} decode_buffer;
+
+typedef struct {
+ char* ptr;
+ int len;
+} thrift_string;
+
+#define read_struct_begin(buf)
+#define read_struct_end(buf)
+
+// This prototype is required to be able to run a call through rb_protect
+// which rescues from ruby exceptions
+static VALUE protectable_consume(VALUE args) {
+ VALUE trans = rb_ary_entry(args, 0);
+ VALUE size = rb_ary_entry(args, 1);
+
+ return rb_funcall(trans, consume_bang_id, 1, size);
+}
+
+// Clears size bytes from the transport's string buffer
+static bool consume(decode_buffer* buf, int32_t size) {
+ if (size != 0) {
+ VALUE ret;
+ VALUE args = rb_ary_new3(2, buf->trans, INT2FIX(size));
+ int status = 0;
+
+ ret = rb_protect(protectable_consume, args, &status);
+
+ if (status) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ // Nothing to consume, we're all good
+ return true;
+}
+
+// This prototype is required to be able to run a call through rb_protect
+// which rescues from ruby exceptions
+static VALUE protectable_borrow(VALUE args) {
+ VALUE trans = rb_ary_entry(args, 0);
+
+ switch(RARRAY(args)->len) {
+ case 1:
+ return rb_funcall(trans, borrow_id, 0);
+
+ case 2: {
+ VALUE size = rb_ary_entry(args, 1);
+ return rb_funcall(trans, borrow_id, 1, size);
+ }
+ }
+
+ return Qnil;
+}
+
+// Calls into the transport to get the available string buffer
+static bool borrow(decode_buffer* buf, int32_t size, VALUE* dst) {
+ int status = 0;
+ VALUE args;
+
+ if (size == 0) {
+ args = rb_ary_new3(1, buf->trans);
+ } else {
+ args = rb_ary_new3(2, buf->trans, INT2FIX(size));
+ }
+
+ *dst = rb_protect(protectable_borrow, args, &status);
+
+ return (status == 0);
+}
+
+// Refills the buffer by calling borrow. If buf->pos is nonzero that number of bytes
+// is cleared through consume.
+//
+// returns: 0 on success, non-zero on failure. On error buf is unchanged.
+static int fill_buffer(decode_buffer* buf, int32_t req_len) {
+ VALUE refill;
+
+ if (!consume(buf, buf->pos)) {
+ return -1;
+ }
+
+ if (!borrow(buf, req_len, &refill)) {
+ return -2;
+ }
+
+ buf->data = StringValuePtr(refill);
+ buf->len = RSTRING(refill)->len;
+ buf->pos = 0;
+
+ return 0;
+}
+
+
+// read_bytes pulls a number of bytes (size) from the buffer, refilling if needed,
+// and places them in dst. This should _always_ be used used when reading from the buffer
+// or buffered transports will be upset with you.
+static bool read_bytes(decode_buffer* buf, void* dst, size_t size) {
+ int avail = (buf->len - buf->pos);
+
+ if (size <= avail) {
+ memcpy(dst, buf->data + buf->pos, size);
+ buf->pos += size;
+ } else {
+
+ if (avail > 0) {
+ // Copy what we can
+ memcpy(dst, buf->data + buf->pos, avail);
+ buf->pos += avail;
+ }
+
+ if (fill_buffer(buf, size - avail) < 0) {
+ return false;
+ }
+
+ memcpy(dst + avail, buf->data, size - avail);
+ buf->pos += size - avail;
+ }
+
+ return true;
+}
+
+// -----------------------------------------------------------------------------
+// Helpers for grabbing specific types from the buffer
+// -----------------------------------------------------------------------------
+
+static bool read_byte(decode_buffer* buf, int8_t* data) {
+ return read_bytes(buf, data, sizeof(int8_t));
+}
+
+static bool read_int16(decode_buffer* buf, int16_t* data) {
+ bool success = read_bytes(buf, data, sizeof(int16_t));
+ *data = ntohs(*data);
+
+ return success;
+}
+
+static bool read_int32(decode_buffer* buf, int32_t* data) {
+ bool success = read_bytes(buf, data, sizeof(int32_t));
+ *data = ntohl(*data);
+
+ return success;
+}
+
+static bool read_int64(decode_buffer* buf, int64_t* data) {
+ bool success = read_bytes(buf, data, sizeof(int64_t));
+ *data = ntohll(*data);
+
+ return success;
+}
+
+static bool read_double(decode_buffer* buf, double* data) {
+ return read_int64(buf, (int64_t*)data);
+}
+
+static bool read_string(decode_buffer* buf, VALUE* data) {
+ int len;
+
+ if (!read_int32(buf, &len)) {
+ return false;
+ }
+
+ if (buf->len - buf->pos >= len) {
+ *data = rb_str_new(buf->data + buf->pos, len);
+ buf->pos += len;
+ }
+ else {
+ char* str;
+
+ if ((str = (char*) malloc(len)) == NULL) {
+ return false;
+ }
+
+ if (!read_bytes(buf, str, len)) {
+ free(str);
+ return false;
+ }
+
+ *data = rb_str_new(str, len);
+
+ free(str);
+ }
+
+ return true;
+}
+
+static bool read_field_begin(decode_buffer* buf, field_header* header) {
+#ifdef __DEBUG__ // No need for this in prod since I set all the fields
+ bzero(header, sizeof(field_header));
+#endif
+
+ header->name = NULL;
+
+ if (!read_byte(buf, &header->type)) {
+ return false;
+ }
+
+ if (header->type == T_STOP) {
+ header->id = 0;
+ } else {
+ if (!read_int16(buf, &header->id)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+#define read_field_end(buf)
+
+static bool read_map_begin(decode_buffer* buf, map_header* header) {
+#ifdef __DEBUG__ // No need for this in prod since I set all the fields
+ bzero(header, sizeof(map_header));
+#endif
+
+ return (read_byte(buf, &header->key_type) &&
+ read_byte(buf, &header->val_type) &&
+ read_int32(buf, &header->num_entries));
+}
+
+#define read_map_end(buf)
+
+static bool read_list_begin(decode_buffer* buf, list_header* header) {
+#ifdef __DEBUG__ // No need for this in prod since I set all the fields
+ bzero(header, sizeof(list_header));
+#endif
+
+ if (!read_byte(buf, &header->type) || !read_int32(buf, &header->num_elements)) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+#define read_list_end(buf)
+
+#define read_set_begin read_list_begin
+#define read_set_end read_list_end
+
+
+// High level reader function with ruby type coercion
+static bool read_type(int type, decode_buffer* buf, VALUE* dst) {
+ switch(type) {
+ case T_BOOL: {
+ int8_t byte;
+
+ if (!read_byte(buf, &byte)) {
+ return false;
+ }
+
+ if (0 == byte) {
+ *dst = Qfalse;
+ } else {
+ *dst = Qtrue;
+ }
+
+ break;
+ }
+
+ case T_BYTE: {
+ int8_t byte;
+
+ if (!read_byte(buf, &byte)) {
+ return false;
+ }
+
+ *dst = INT2FIX(byte);
+ break;
+ }
+
+ case T_I16: {
+ int16_t i16;
+
+ if (!read_int16(buf, &i16)) {
+ return false;
+ }
+
+ *dst = INT2FIX(i16);
+ break;
+ }
+
+ case T_I32: {
+ int32_t i32;
+
+ if (!read_int32(buf, &i32)) {
+ return false;
+ }
+
+ *dst = INT2NUM(i32);
+ break;
+ }
+
+ case T_I64: {
+ int64_t i64;
+
+ if (!read_int64(buf, &i64)) {
+ return false;
+ }
+
+ *dst = rb_ll2inum(i64);
+ break;
+ }
+
+ case T_DBL: {
+ double dbl;
+
+ if (!read_double(buf, &dbl)) {
+ return false;
+ }
+
+ *dst = rb_float_new(dbl);
+ break;
+ }
+
+ case T_STR: {
+ VALUE str;
+
+ if (!read_string(buf, &str)) {
+ return false;
+ }
+
+ *dst = str;
+ break;
+ }
+ }
+
+ return true;
+}
+
+// TODO(kevinclark): Now that read_string does a malloc,
+// This maybe could be modified to avoid that, and the type coercion
+
+// Read the bytes but don't do anything with the value
+static bool skip_type(int type, decode_buffer* buf) {
+ switch (type) {
+ case T_STRCT:
+ read_struct_begin(buf);
+ while (true) {
+ field_header header;
+
+ if (!read_field_begin(buf, &header)) {
+ return false;
+ }
+
+ if (header.type == T_STOP) {
+ break;
+ }
+
+ if (!skip_type(header.type, buf)) {
+ return false;
+ }
+
+ read_field_end(buf);
+ }
+ read_struct_end(buf);
+
+ break;
+
+ case T_MAP: {
+ int i;
+ map_header header;
+
+ if (!read_map_begin(buf, &header)) {
+ return false;
+ }
+
+ for (i = 0; i < header.num_entries; ++i) {
+ if (!skip_type(header.key_type, buf)) {
+ return false;
+ }
+ if (!skip_type(header.val_type, buf)) {
+ return false;
+ }
+ }
+
+ read_map_end(buf);
+ break;
+ }
+
+ case T_SET: {
+ int i;
+ set_header header;
+
+ if (!read_set_begin(buf, &header)) {
+ return false;
+ }
+
+ for (i = 0; i < header.num_elements; ++i) {
+ if (!skip_type(header.type, buf)) {
+ return false;
+ }
+ }
+
+ read_set_end(buf);
+ break;
+ }
+
+ case T_LIST: {
+ int i;
+ list_header header;
+
+ if (!read_list_begin(buf, &header)) {
+ return false;
+ }
+
+ for (i = 0; i < header.num_elements; ++i) {
+ if (!skip_type(header.type, buf)) {
+ return false;
+ }
+ }
+
+ read_list_end(buf);
+ break;
+ }
+
+ default: {
+ VALUE v;
+ if (!read_type(type, buf, &v)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+static VALUE read_struct(VALUE obj, decode_buffer* buf);
+
+// Read the right thing from the buffer given the field spec
+// and return the ruby object
+static bool read_field(decode_buffer* buf, field_spec* spec, VALUE* dst) {
+ switch (spec->type) {
+ case T_STRCT: {
+ VALUE obj = rb_class_new_instance(0, NULL, spec->data.class);
+
+ *dst = read_struct(obj, buf);
+ break;
+ }
+
+ case T_MAP: {
+ map_header hdr;
+ VALUE hsh;
+ int i;
+
+ read_map_begin(buf, &hdr);
+ hsh = rb_hash_new();
+
+ for (i = 0; i < hdr.num_entries; ++i) {
+ VALUE key, val;
+
+ if (!read_field(buf, spec->data.map->key, &key)) {
+ return false;
+ }
+
+ if (!read_field(buf, spec->data.map->value, &val)) {
+ return false;
+ }
+
+ rb_hash_aset(hsh, key, val);
+ }
+
+ read_map_end(buf);
+
+ *dst = hsh;
+ break;
+ }
+
+ case T_LIST: {
+ list_header hdr;
+ VALUE arr, element;
+ int i;
+
+ read_list_begin(buf, &hdr);
+ arr = rb_ary_new2(hdr.num_elements);
+
+ for (i = 0; i < hdr.num_elements; ++i) {
+ if (!read_field(buf, spec->data.element, &element)) {
+ return false;
+ }
+
+ rb_ary_push(arr, element);
+ }
+
+ read_list_end(buf);
+
+ *dst = arr;
+ break;
+ }
+
+ case T_SET: {
+ VALUE items, item;
+ set_header hdr;
+ int i;
+
+ read_set_begin(buf, &hdr);
+ items = rb_ary_new2(hdr.num_elements);
+
+ for (i = 0; i < hdr.num_elements; ++i) {
+ if (!read_field(buf, spec->data.element, &item)) {
+ return false;
+ }
+
+ rb_ary_push(items, item);
+ }
+
+ *dst = rb_class_new_instance(1, &items, rb_cSet);
+ break;
+ }
+
+
+ default:
+ return read_type(spec->type, buf, dst);
+ }
+
+ return true;
+}
+
+static void handle_read_error() {
+ // If it was an exception, reraise
+ if (!NIL_P(ruby_errinfo)) {
+ rb_exc_raise(ruby_errinfo);
+ } else {
+ // Something else went wrong, no idea what would call this yet
+ // So far, the only thing to cause failures underneath is ruby
+ // exceptions. Follow up on this regularly -- Kevin Clark (TODO)
+ rb_raise(rb_eStandardError, "[BUG] Something went wrong in the field reading, but not a ruby exception");
+ }
+}
+
+// Fill in the instance variables in an object (thrift struct)
+// from the decode buffer
+static VALUE read_struct(VALUE obj, decode_buffer* buf) {
+ VALUE field;
+ field_header f_header;
+ VALUE value = Qnil;
+ VALUE fields = rb_const_get(CLASS_OF(obj), fields_id);
+ field_spec* spec;
+ char name_buf[128];
+
+ read_struct_begin(buf);
+
+ while(true) {
+ if (!read_field_begin(buf, &f_header)) {
+ handle_read_error();
+ }
+
+ if (T_STOP == f_header.type) {
+ break;
+ }
+
+ field = rb_hash_aref(fields, INT2FIX(f_header.id));
+
+ if (NIL_P(field)) {
+ if (!skip_type(f_header.type, buf)) {
+ handle_read_error();
+ return Qnil;
+ }
+ }
+ else {
+ spec = parse_field_spec(field);
+
+ if (spec->type != f_header.type) {
+ if (!skip_type(spec->type, buf)) {
+ free_field_spec(spec);
+ handle_read_error();
+ return Qnil;
+ }
+ } else {
+ // Read busted somewhere (probably borrow/consume), bail
+ if (!read_field(buf, spec, &value)) {
+ free_field_spec(spec);
+ handle_read_error();
+ return Qnil;
+ }
+
+ name_buf[0] = '@';
+ strlcpy(&name_buf[1], spec->name, sizeof(name_buf) - 1);
+
+ rb_iv_set(obj, name_buf, value);
+ }
+
+ free_field_spec(spec);
+ }
+
+ read_field_end(buf);
+ }
+
+ read_struct_end(buf);
+
+ return obj;
+}
+
+
+// Takes an object and transport, and decodes the values in the transport's
+// buffer to fill the object.
+static VALUE tbpa_decode_binary(VALUE self, VALUE obj, VALUE transport) {
+ decode_buffer buf;
+ VALUE ret_val;
+
+ buf.pos = 0; // This needs to be set so an arbitrary number of bytes isn't consumed
+ buf.trans = transport; // We need to hold this so the buffer can be refilled
+
+ if (fill_buffer(&buf, 0) < 0) {
+ handle_read_error();
+ return Qnil;
+ }
+
+#ifdef __DEBUG__
+ rb_p(rb_str_new2("Running decode binary with data:"));
+ rb_p(rb_inspect(rb_str_new2(buf.data)));
+#endif
+
+ ret_val = read_struct(obj, &buf);
+
+ // Consume whatever was read
+ consume(&buf, buf.pos);
+
+ return ret_val;
+}
+
+// -----------------------------------------------------------------------------
+// These methods are used by the thrift library and need to handled
+// seperately from encode and decode
+// -----------------------------------------------------------------------------
+
+// Read the message header and return it as a ruby array
+static VALUE tbpa_read_message_begin(VALUE self) {
+ decode_buffer buf;
+ int32_t version, seqid;
+ int8_t type;
+ VALUE name;
+
+ VALUE trans = rb_iv_get(self, "@trans");
+
+ buf.pos = 0; // This needs to be set so fill_buffer doesn't consume
+ buf.trans = trans; // We need to hold this so the buffer can be refilled
+
+
+ if (fill_buffer(&buf, 0) < 0 || !read_int32(&buf, &version)) {
+ // Consume whatever was read
+ consume(&buf, buf.pos);
+ handle_read_error();
+ return Qnil;
+ }
+
+ if ((version & VERSION_MASK) != VERSION_1) {
+ VALUE tprotocol_exception = rb_const_get(rb_cObject, rb_intern("TProtocolException"));
+ VALUE exception = rb_funcall(tprotocol_exception, rb_intern("new"), 2, rb_const_get(tprotocol_exception, rb_intern("BAD_VERSION")), rb_str_new2("Missing version identifier"));
+ rb_raise(exception, "");
+ }
+
+ type = version & 0x000000ff;
+
+ if (!read_string(&buf, &name) || !read_int32(&buf, &seqid)) {
+ // Consume whatever was read
+ consume(&buf, buf.pos);
+ handle_read_error();
+ return Qnil;
+ }
+
+ // Consume whatever was read
+ if (consume(&buf, buf.pos) < 0) {
+ handle_read_error();
+ return Qnil;
+ }
+
+ return rb_ary_new3(3, name, INT2FIX(type), INT2FIX(seqid));
+}
+
+void Init_binaryprotocolaccelerated()
+{
+ m_thrift = rb_const_get(rb_cObject, rb_intern("Thrift"));
+ VALUE class_tbinproto = rb_const_get(m_thrift, rb_intern("BinaryProtocol"));
+ class_tbpa = rb_define_class_under(m_thrift, "BinaryProtocolAccelerated", class_tbinproto);
+ type_sym = ID2SYM(rb_intern("type"));
+ class_sym = ID2SYM(rb_intern("class"));
+ key_sym = ID2SYM(rb_intern("key"));
+ value_sym = ID2SYM(rb_intern("value"));
+ name_sym = ID2SYM(rb_intern("name"));
+ fields_id = rb_intern("FIELDS");
+ element_sym = ID2SYM(rb_intern("element"));
+ consume_bang_id = rb_intern("consume!");
+ string_buffer_id = rb_intern("string_buffer");
+ borrow_id = rb_intern("borrow");
+ keys_id = rb_intern("keys");
+ entries_id = rb_intern("entries");
+ rb_cSet = rb_const_get(rb_cObject, rb_intern("Set"));
+
+ // For fast access
+ rb_define_method(class_tbpa, "encode_binary", tbpa_encode_binary, 1);
+ rb_define_method(class_tbpa, "decode_binary", tbpa_decode_binary, 2);
+ rb_define_method(class_tbpa, "read_message_begin", tbpa_read_message_begin, 0);
+
+}
diff --git a/lib/rb/ext/extconf.rb b/lib/rb/ext/extconf.rb
new file mode 100644
index 0000000..54ad5ed
--- /dev/null
+++ b/lib/rb/ext/extconf.rb
@@ -0,0 +1,7 @@
+require 'mkmf'
+
+$CFLAGS = "-g -O2 -Wall -Werror"
+
+have_func("strlcpy", "string.h")
+
+create_makefile 'binaryprotocolaccelerated'
diff --git a/lib/rb/lib/thrift/protocol/binaryprotocolaccelerated.rb b/lib/rb/lib/thrift/protocol/binaryprotocolaccelerated.rb
new file mode 100644
index 0000000..fae9819
--- /dev/null
+++ b/lib/rb/lib/thrift/protocol/binaryprotocolaccelerated.rb
@@ -0,0 +1,19 @@
+require 'thrift/protocol/binaryprotocol'
+require 'binaryprotocolaccelerated'
+
+=begin
+The only change required for a transport to support TBinaryProtocolAccelerated is to implement 2 methods:
+ * borrow(size), which takes an optional argument and returns atleast _size_ bytes from the transport,
+ or the default buffer size if no argument is given
+ * consume!(size), which removes size bytes from the front of the buffer
+
+See TMemoryBuffer and TBufferedTransport for examples.
+=end
+
+module Thrift
+ class BinaryProtocolAcceleratedFactory < ProtocolFactory
+ def get_protocol(trans)
+ BinaryProtocolAccelerated.new(trans)
+ end
+ end
+end
diff --git a/lib/rb/lib/thrift/server/nonblockingserver.rb b/lib/rb/lib/thrift/server/nonblockingserver.rb
index dc0f646..df199cc 100644
--- a/lib/rb/lib/thrift/server/nonblockingserver.rb
+++ b/lib/rb/lib/thrift/server/nonblockingserver.rb
@@ -72,6 +72,8 @@
end
class IOManager # :nodoc:
+ DEFAULT_BUFFER = 2**20
+
def initialize(processor, serverTransport, transportFactory, protocolFactory, num, logger)
@processor = processor
@serverTransport = serverTransport
@@ -116,7 +118,7 @@
end
private
-
+
def run
spin_worker_threads
@@ -139,7 +141,7 @@
end
def read_connection(fd)
- @buffers[fd] << fd.readpartial(1048576)
+ @buffers[fd] << fd.read(DEFAULT_BUFFER)
frame = slice_frame!(@buffers[fd])
if frame
@logger.debug "#{self} is processing a frame"
diff --git a/lib/rb/lib/thrift/struct.rb b/lib/rb/lib/thrift/struct.rb
index b016f13..0535948 100644
--- a/lib/rb/lib/thrift/struct.rb
+++ b/lib/rb/lib/thrift/struct.rb
@@ -23,31 +23,41 @@
end
def read(iprot)
- iprot.read_struct_begin
- loop do
- fname, ftype, fid = iprot.read_field_begin
- break if (ftype == Types::STOP)
- handle_message(iprot, fid, ftype)
- iprot.read_field_end
+ # TODO(kevinclark): Make sure transport is C readable
+ if iprot.respond_to?(:decode_binary)
+ iprot.decode_binary(self, iprot.trans)
+ else
+ iprot.read_struct_begin
+ loop do
+ fname, ftype, fid = iprot.read_field_begin
+ break if (ftype == Types::STOP)
+ handle_message(iprot, fid, ftype)
+ iprot.read_field_end
+ end
+ iprot.read_struct_end
end
- iprot.read_struct_end
end
def write(oprot)
- oprot.write_struct_begin(self.class.name)
- each_field do |fid, type, name|
- unless (value = instance_variable_get("@#{name}")).nil?
- if is_container? type
- oprot.write_field_begin(name, type, fid)
- write_container(oprot, value, struct_fields[fid])
- oprot.write_field_end
- else
- oprot.write_field(name, type, fid, value)
+ if oprot.respond_to?(:encode_binary)
+ # TODO(kevinclark): Clean this so I don't have to access the transport.
+ oprot.trans.write oprot.encode_binary(self)
+ else
+ oprot.write_struct_begin(self.class.name)
+ each_field do |fid, type, name|
+ unless (value = instance_variable_get("@#{name}")).nil?
+ if is_container? type
+ oprot.write_field_begin(name, type, fid)
+ write_container(oprot, value, struct_fields[fid])
+ oprot.write_field_end
+ else
+ oprot.write_field(name, type, fid, value)
+ end
end
end
+ oprot.write_field_stop
+ oprot.write_struct_end
end
- oprot.write_field_stop
- oprot.write_struct_end
end
def ==(other)
diff --git a/lib/rb/lib/thrift/transport.rb b/lib/rb/lib/thrift/transport.rb
index 36492dd..d55adf0 100644
--- a/lib/rb/lib/thrift/transport.rb
+++ b/lib/rb/lib/thrift/transport.rb
@@ -76,9 +76,12 @@
deprecate_class! :TTransportFactory => TransportFactory
class BufferedTransport < Transport
+ DEFAULT_BUFFER = 4096
+
def initialize(transport)
@transport = transport
@wbuf = ''
+ @rbuf = ''
end
def open?
@@ -95,7 +98,13 @@
end
def read(sz)
- return @transport.read(sz)
+ ret = @rbuf.slice!(0...sz)
+ if ret.length == 0
+ @rbuf = @transport.read([sz, DEFAULT_BUFFER].max)
+ @rbuf.slice!(0...sz)
+ else
+ ret
+ end
end
def write(buf)
@@ -110,6 +119,25 @@
@transport.flush
end
+
+ def borrow(requested_length = 0)
+ # $stderr.puts "#{Time.now.to_f} Have #{@rbuf.length} asking for #{requested_length.inspect}"
+ return @rbuf if @rbuf.length > requested_length
+
+ if @rbuf.length < DEFAULT_BUFFER
+ @rbuf << @transport.read([requested_length, DEFAULT_BUFFER].max)
+ end
+
+ if @rbuf.length < requested_length
+ @rbuf << @transport.read_all(requested_length - @rbuf.length)
+ end
+
+ @rbuf
+ end
+
+ def consume!(size)
+ @rbuf.slice!(0...size)
+ end
end
deprecate_class! :TBufferedTransport => BufferedTransport
@@ -230,6 +258,23 @@
def flush
end
+
+ # For fast binary protocol access
+ def borrow(size = nil)
+ if size.nil?
+ @buf[0..-1]
+ else
+ if size > @buf.length
+ raise EOFError # Memory buffers only get one shot.
+ else
+ @buf[0..size]
+ end
+ end
+ end
+
+ def consume!(size)
+ @buf.slice!(0, size)
+ end
end
deprecate_class! :TMemoryBuffer => MemoryBuffer
diff --git a/lib/rb/lib/thrift/transport/socket.rb b/lib/rb/lib/thrift/transport/socket.rb
index e3981fe..c7ed521 100644
--- a/lib/rb/lib/thrift/transport/socket.rb
+++ b/lib/rb/lib/thrift/transport/socket.rb
@@ -43,17 +43,11 @@
end
end
- def read(sz, partial=false)
+ def read(sz)
raise IOError, "closed stream" unless open?
+
begin
- if partial
- data = @handle.readpartial(sz)
- else
- data = @handle.read(sz)
- end
- rescue Errno::EAGAIN => e
- # let our parent know that the nonblock read failed
- raise e
+ data = @handle.readpartial(sz)
rescue StandardError => e
@handle.close unless @handle.closed?
@handle = nil
@@ -65,10 +59,6 @@
data
end
- def readpartial(sz)
- read(sz, true)
- end
-
def close
@handle.close unless @handle.nil? or @handle.closed?
@handle = nil
diff --git a/lib/rb/setup.rb b/lib/rb/setup.rb
new file mode 100644
index 0000000..9f0c826
--- /dev/null
+++ b/lib/rb/setup.rb
@@ -0,0 +1,1585 @@
+#
+# setup.rb
+#
+# Copyright (c) 2000-2005 Minero Aoki
+#
+# This program is free software.
+# You can distribute/modify this program under the terms of
+# the GNU LGPL, Lesser General Public License version 2.1.
+#
+
+unless Enumerable.method_defined?(:map) # Ruby 1.4.6
+ module Enumerable
+ alias map collect
+ end
+end
+
+unless File.respond_to?(:read) # Ruby 1.6
+ def File.read(fname)
+ open(fname) {|f|
+ return f.read
+ }
+ end
+end
+
+unless Errno.const_defined?(:ENOTEMPTY) # Windows?
+ module Errno
+ class ENOTEMPTY
+ # We do not raise this exception, implementation is not needed.
+ end
+ end
+end
+
+def File.binread(fname)
+ open(fname, 'rb') {|f|
+ return f.read
+ }
+end
+
+# for corrupted Windows' stat(2)
+def File.dir?(path)
+ File.directory?((path[-1,1] == '/') ? path : path + '/')
+end
+
+
+class ConfigTable
+
+ include Enumerable
+
+ def initialize(rbconfig)
+ @rbconfig = rbconfig
+ @items = []
+ @table = {}
+ # options
+ @install_prefix = nil
+ @config_opt = nil
+ @verbose = true
+ @no_harm = false
+ end
+
+ attr_accessor :install_prefix
+ attr_accessor :config_opt
+
+ attr_writer :verbose
+
+ def verbose?
+ @verbose
+ end
+
+ attr_writer :no_harm
+
+ def no_harm?
+ @no_harm
+ end
+
+ def [](key)
+ lookup(key).resolve(self)
+ end
+
+ def []=(key, val)
+ lookup(key).set val
+ end
+
+ def names
+ @items.map {|i| i.name }
+ end
+
+ def each(&block)
+ @items.each(&block)
+ end
+
+ def key?(name)
+ @table.key?(name)
+ end
+
+ def lookup(name)
+ @table[name] or setup_rb_error "no such config item: #{name}"
+ end
+
+ def add(item)
+ @items.push item
+ @table[item.name] = item
+ end
+
+ def remove(name)
+ item = lookup(name)
+ @items.delete_if {|i| i.name == name }
+ @table.delete_if {|name, i| i.name == name }
+ item
+ end
+
+ def load_script(path, inst = nil)
+ if File.file?(path)
+ MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path
+ end
+ end
+
+ def savefile
+ '.config'
+ end
+
+ def load_savefile
+ begin
+ File.foreach(savefile()) do |line|
+ k, v = *line.split(/=/, 2)
+ self[k] = v.strip
+ end
+ rescue Errno::ENOENT
+ setup_rb_error $!.message + "\n#{File.basename($0)} config first"
+ end
+ end
+
+ def save
+ @items.each {|i| i.value }
+ File.open(savefile(), 'w') {|f|
+ @items.each do |i|
+ f.printf "%s=%s\n", i.name, i.value if i.value? and i.value
+ end
+ }
+ end
+
+ def load_standard_entries
+ standard_entries(@rbconfig).each do |ent|
+ add ent
+ end
+ end
+
+ def standard_entries(rbconfig)
+ c = rbconfig
+
+ rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT'])
+
+ major = c['MAJOR'].to_i
+ minor = c['MINOR'].to_i
+ teeny = c['TEENY'].to_i
+ version = "#{major}.#{minor}"
+
+ # ruby ver. >= 1.4.4?
+ newpath_p = ((major >= 2) or
+ ((major == 1) and
+ ((minor >= 5) or
+ ((minor == 4) and (teeny >= 4)))))
+
+ if c['rubylibdir']
+ # V > 1.6.3
+ libruby = "#{c['prefix']}/lib/ruby"
+ librubyver = c['rubylibdir']
+ librubyverarch = c['archdir']
+ siteruby = c['sitedir']
+ siterubyver = c['sitelibdir']
+ siterubyverarch = c['sitearchdir']
+ elsif newpath_p
+ # 1.4.4 <= V <= 1.6.3
+ libruby = "#{c['prefix']}/lib/ruby"
+ librubyver = "#{c['prefix']}/lib/ruby/#{version}"
+ librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
+ siteruby = c['sitedir']
+ siterubyver = "$siteruby/#{version}"
+ siterubyverarch = "$siterubyver/#{c['arch']}"
+ else
+ # V < 1.4.4
+ libruby = "#{c['prefix']}/lib/ruby"
+ librubyver = "#{c['prefix']}/lib/ruby/#{version}"
+ librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
+ siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby"
+ siterubyver = siteruby
+ siterubyverarch = "$siterubyver/#{c['arch']}"
+ end
+ parameterize = lambda {|path|
+ path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')
+ }
+
+ if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
+ makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
+ else
+ makeprog = 'make'
+ end
+
+ [
+ ExecItem.new('installdirs', 'std/site/home',
+ 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\
+ {|val, table|
+ case val
+ when 'std'
+ table['rbdir'] = '$librubyver'
+ table['sodir'] = '$librubyverarch'
+ when 'site'
+ table['rbdir'] = '$siterubyver'
+ table['sodir'] = '$siterubyverarch'
+ when 'home'
+ setup_rb_error '$HOME was not set' unless ENV['HOME']
+ table['prefix'] = ENV['HOME']
+ table['rbdir'] = '$libdir/ruby'
+ table['sodir'] = '$libdir/ruby'
+ end
+ },
+ PathItem.new('prefix', 'path', c['prefix'],
+ 'path prefix of target environment'),
+ PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
+ 'the directory for commands'),
+ PathItem.new('libdir', 'path', parameterize.call(c['libdir']),
+ 'the directory for libraries'),
+ PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
+ 'the directory for shared data'),
+ PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
+ 'the directory for man pages'),
+ PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
+ 'the directory for system configuration files'),
+ PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']),
+ 'the directory for local state data'),
+ PathItem.new('libruby', 'path', libruby,
+ 'the directory for ruby libraries'),
+ PathItem.new('librubyver', 'path', librubyver,
+ 'the directory for standard ruby libraries'),
+ PathItem.new('librubyverarch', 'path', librubyverarch,
+ 'the directory for standard ruby extensions'),
+ PathItem.new('siteruby', 'path', siteruby,
+ 'the directory for version-independent aux ruby libraries'),
+ PathItem.new('siterubyver', 'path', siterubyver,
+ 'the directory for aux ruby libraries'),
+ PathItem.new('siterubyverarch', 'path', siterubyverarch,
+ 'the directory for aux ruby binaries'),
+ PathItem.new('rbdir', 'path', '$siterubyver',
+ 'the directory for ruby scripts'),
+ PathItem.new('sodir', 'path', '$siterubyverarch',
+ 'the directory for ruby extentions'),
+ PathItem.new('rubypath', 'path', rubypath,
+ 'the path to set to #! line'),
+ ProgramItem.new('rubyprog', 'name', rubypath,
+ 'the ruby program using for installation'),
+ ProgramItem.new('makeprog', 'name', makeprog,
+ 'the make program to compile ruby extentions'),
+ SelectItem.new('shebang', 'all/ruby/never', 'ruby',
+ 'shebang line (#!) editing mode'),
+ BoolItem.new('without-ext', 'yes/no', 'no',
+ 'does not compile/install ruby extentions')
+ ]
+ end
+ private :standard_entries
+
+ def load_multipackage_entries
+ multipackage_entries().each do |ent|
+ add ent
+ end
+ end
+
+ def multipackage_entries
+ [
+ PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
+ 'package names that you want to install'),
+ PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
+ 'package names that you do not want to install')
+ ]
+ end
+ private :multipackage_entries
+
+ ALIASES = {
+ 'std-ruby' => 'librubyver',
+ 'stdruby' => 'librubyver',
+ 'rubylibdir' => 'librubyver',
+ 'archdir' => 'librubyverarch',
+ 'site-ruby-common' => 'siteruby', # For backward compatibility
+ 'site-ruby' => 'siterubyver', # For backward compatibility
+ 'bin-dir' => 'bindir',
+ 'bin-dir' => 'bindir',
+ 'rb-dir' => 'rbdir',
+ 'so-dir' => 'sodir',
+ 'data-dir' => 'datadir',
+ 'ruby-path' => 'rubypath',
+ 'ruby-prog' => 'rubyprog',
+ 'ruby' => 'rubyprog',
+ 'make-prog' => 'makeprog',
+ 'make' => 'makeprog'
+ }
+
+ def fixup
+ ALIASES.each do |ali, name|
+ @table[ali] = @table[name]
+ end
+ @items.freeze
+ @table.freeze
+ @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/
+ end
+
+ def parse_opt(opt)
+ m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}"
+ m.to_a[1,2]
+ end
+
+ def dllext
+ @rbconfig['DLEXT']
+ end
+
+ def value_config?(name)
+ lookup(name).value?
+ end
+
+ class Item
+ def initialize(name, template, default, desc)
+ @name = name.freeze
+ @template = template
+ @value = default
+ @default = default
+ @description = desc
+ end
+
+ attr_reader :name
+ attr_reader :description
+
+ attr_accessor :default
+ alias help_default default
+
+ def help_opt
+ "--#{@name}=#{@template}"
+ end
+
+ def value?
+ true
+ end
+
+ def value
+ @value
+ end
+
+ def resolve(table)
+ @value.gsub(%r<\$([^/]+)>) { table[$1] }
+ end
+
+ def set(val)
+ @value = check(val)
+ end
+
+ private
+
+ def check(val)
+ setup_rb_error "config: --#{name} requires argument" unless val
+ val
+ end
+ end
+
+ class BoolItem < Item
+ def config_type
+ 'bool'
+ end
+
+ def help_opt
+ "--#{@name}"
+ end
+
+ private
+
+ def check(val)
+ return 'yes' unless val
+ case val
+ when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes'
+ when /\An(o)?\z/i, /\Af(alse)\z/i then 'no'
+ else
+ setup_rb_error "config: --#{@name} accepts only yes/no for argument"
+ end
+ end
+ end
+
+ class PathItem < Item
+ def config_type
+ 'path'
+ end
+
+ private
+
+ def check(path)
+ setup_rb_error "config: --#{@name} requires argument" unless path
+ path[0,1] == '$' ? path : File.expand_path(path)
+ end
+ end
+
+ class ProgramItem < Item
+ def config_type
+ 'program'
+ end
+ end
+
+ class SelectItem < Item
+ def initialize(name, selection, default, desc)
+ super
+ @ok = selection.split('/')
+ end
+
+ def config_type
+ 'select'
+ end
+
+ private
+
+ def check(val)
+ unless @ok.include?(val.strip)
+ setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
+ end
+ val.strip
+ end
+ end
+
+ class ExecItem < Item
+ def initialize(name, selection, desc, &block)
+ super name, selection, nil, desc
+ @ok = selection.split('/')
+ @action = block
+ end
+
+ def config_type
+ 'exec'
+ end
+
+ def value?
+ false
+ end
+
+ def resolve(table)
+ setup_rb_error "$#{name()} wrongly used as option value"
+ end
+
+ undef set
+
+ def evaluate(val, table)
+ v = val.strip.downcase
+ unless @ok.include?(v)
+ setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})"
+ end
+ @action.call v, table
+ end
+ end
+
+ class PackageSelectionItem < Item
+ def initialize(name, template, default, help_default, desc)
+ super name, template, default, desc
+ @help_default = help_default
+ end
+
+ attr_reader :help_default
+
+ def config_type
+ 'package'
+ end
+
+ private
+
+ def check(val)
+ unless File.dir?("packages/#{val}")
+ setup_rb_error "config: no such package: #{val}"
+ end
+ val
+ end
+ end
+
+ class MetaConfigEnvironment
+ def initialize(config, installer)
+ @config = config
+ @installer = installer
+ end
+
+ def config_names
+ @config.names
+ end
+
+ def config?(name)
+ @config.key?(name)
+ end
+
+ def bool_config?(name)
+ @config.lookup(name).config_type == 'bool'
+ end
+
+ def path_config?(name)
+ @config.lookup(name).config_type == 'path'
+ end
+
+ def value_config?(name)
+ @config.lookup(name).config_type != 'exec'
+ end
+
+ def add_config(item)
+ @config.add item
+ end
+
+ def add_bool_config(name, default, desc)
+ @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
+ end
+
+ def add_path_config(name, default, desc)
+ @config.add PathItem.new(name, 'path', default, desc)
+ end
+
+ def set_config_default(name, default)
+ @config.lookup(name).default = default
+ end
+
+ def remove_config(name)
+ @config.remove(name)
+ end
+
+ # For only multipackage
+ def packages
+ raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer
+ @installer.packages
+ end
+
+ # For only multipackage
+ def declare_packages(list)
+ raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer
+ @installer.packages = list
+ end
+ end
+
+end # class ConfigTable
+
+
+# This module requires: #verbose?, #no_harm?
+module FileOperations
+
+ def mkdir_p(dirname, prefix = nil)
+ dirname = prefix + File.expand_path(dirname) if prefix
+ $stderr.puts "mkdir -p #{dirname}" if verbose?
+ return if no_harm?
+
+ # Does not check '/', it's too abnormal.
+ dirs = File.expand_path(dirname).split(%r<(?=/)>)
+ if /\A[a-z]:\z/i =~ dirs[0]
+ disk = dirs.shift
+ dirs[0] = disk + dirs[0]
+ end
+ dirs.each_index do |idx|
+ path = dirs[0..idx].join('')
+ Dir.mkdir path unless File.dir?(path)
+ end
+ end
+
+ def rm_f(path)
+ $stderr.puts "rm -f #{path}" if verbose?
+ return if no_harm?
+ force_remove_file path
+ end
+
+ def rm_rf(path)
+ $stderr.puts "rm -rf #{path}" if verbose?
+ return if no_harm?
+ remove_tree path
+ end
+
+ def remove_tree(path)
+ if File.symlink?(path)
+ remove_file path
+ elsif File.dir?(path)
+ remove_tree0 path
+ else
+ force_remove_file path
+ end
+ end
+
+ def remove_tree0(path)
+ Dir.foreach(path) do |ent|
+ next if ent == '.'
+ next if ent == '..'
+ entpath = "#{path}/#{ent}"
+ if File.symlink?(entpath)
+ remove_file entpath
+ elsif File.dir?(entpath)
+ remove_tree0 entpath
+ else
+ force_remove_file entpath
+ end
+ end
+ begin
+ Dir.rmdir path
+ rescue Errno::ENOTEMPTY
+ # directory may not be empty
+ end
+ end
+
+ def move_file(src, dest)
+ force_remove_file dest
+ begin
+ File.rename src, dest
+ rescue
+ File.open(dest, 'wb') {|f|
+ f.write File.binread(src)
+ }
+ File.chmod File.stat(src).mode, dest
+ File.unlink src
+ end
+ end
+
+ def force_remove_file(path)
+ begin
+ remove_file path
+ rescue
+ end
+ end
+
+ def remove_file(path)
+ File.chmod 0777, path
+ File.unlink path
+ end
+
+ def install(from, dest, mode, prefix = nil)
+ $stderr.puts "install #{from} #{dest}" if verbose?
+ return if no_harm?
+
+ realdest = prefix ? prefix + File.expand_path(dest) : dest
+ realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
+ str = File.binread(from)
+ if diff?(str, realdest)
+ verbose_off {
+ rm_f realdest if File.exist?(realdest)
+ }
+ File.open(realdest, 'wb') {|f|
+ f.write str
+ }
+ File.chmod mode, realdest
+
+ File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
+ if prefix
+ f.puts realdest.sub(prefix, '')
+ else
+ f.puts realdest
+ end
+ }
+ end
+ end
+
+ def diff?(new_content, path)
+ return true unless File.exist?(path)
+ new_content != File.binread(path)
+ end
+
+ def command(*args)
+ $stderr.puts args.join(' ') if verbose?
+ system(*args) or raise RuntimeError,
+ "system(#{args.map{|a| a.inspect }.join(' ')}) failed"
+ end
+
+ def ruby(*args)
+ command config('rubyprog'), *args
+ end
+
+ def make(task = nil)
+ command(*[config('makeprog'), task].compact)
+ end
+
+ def extdir?(dir)
+ File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb")
+ end
+
+ def files_of(dir)
+ Dir.open(dir) {|d|
+ return d.select {|ent| File.file?("#{dir}/#{ent}") }
+ }
+ end
+
+ DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn )
+
+ def directories_of(dir)
+ Dir.open(dir) {|d|
+ return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT
+ }
+ end
+
+end
+
+
+# This module requires: #srcdir_root, #objdir_root, #relpath
+module HookScriptAPI
+
+ def get_config(key)
+ @config[key]
+ end
+
+ alias config get_config
+
+ # obsolete: use metaconfig to change configuration
+ def set_config(key, val)
+ @config[key] = val
+ end
+
+ #
+ # srcdir/objdir (works only in the package directory)
+ #
+
+ def curr_srcdir
+ "#{srcdir_root()}/#{relpath()}"
+ end
+
+ def curr_objdir
+ "#{objdir_root()}/#{relpath()}"
+ end
+
+ def srcfile(path)
+ "#{curr_srcdir()}/#{path}"
+ end
+
+ def srcexist?(path)
+ File.exist?(srcfile(path))
+ end
+
+ def srcdirectory?(path)
+ File.dir?(srcfile(path))
+ end
+
+ def srcfile?(path)
+ File.file?(srcfile(path))
+ end
+
+ def srcentries(path = '.')
+ Dir.open("#{curr_srcdir()}/#{path}") {|d|
+ return d.to_a - %w(. ..)
+ }
+ end
+
+ def srcfiles(path = '.')
+ srcentries(path).select {|fname|
+ File.file?(File.join(curr_srcdir(), path, fname))
+ }
+ end
+
+ def srcdirectories(path = '.')
+ srcentries(path).select {|fname|
+ File.dir?(File.join(curr_srcdir(), path, fname))
+ }
+ end
+
+end
+
+
+class ToplevelInstaller
+
+ Version = '3.4.1'
+ Copyright = 'Copyright (c) 2000-2005 Minero Aoki'
+
+ TASKS = [
+ [ 'all', 'do config, setup, then install' ],
+ [ 'config', 'saves your configurations' ],
+ [ 'show', 'shows current configuration' ],
+ [ 'setup', 'compiles ruby extentions and others' ],
+ [ 'install', 'installs files' ],
+ [ 'test', 'run all tests in test/' ],
+ [ 'clean', "does `make clean' for each extention" ],
+ [ 'distclean',"does `make distclean' for each extention" ]
+ ]
+
+ def ToplevelInstaller.invoke
+ config = ConfigTable.new(load_rbconfig())
+ config.load_standard_entries
+ config.load_multipackage_entries if multipackage?
+ config.fixup
+ klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller)
+ klass.new(File.dirname($0), config).invoke
+ end
+
+ def ToplevelInstaller.multipackage?
+ File.dir?(File.dirname($0) + '/packages')
+ end
+
+ def ToplevelInstaller.load_rbconfig
+ if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
+ ARGV.delete(arg)
+ load File.expand_path(arg.split(/=/, 2)[1])
+ $".push 'rbconfig.rb'
+ else
+ require 'rbconfig'
+ end
+ ::Config::CONFIG
+ end
+
+ def initialize(ardir_root, config)
+ @ardir = File.expand_path(ardir_root)
+ @config = config
+ # cache
+ @valid_task_re = nil
+ end
+
+ def config(key)
+ @config[key]
+ end
+
+ def inspect
+ "#<#{self.class} #{__id__()}>"
+ end
+
+ def invoke
+ run_metaconfigs
+ case task = parsearg_global()
+ when nil, 'all'
+ parsearg_config
+ init_installers
+ exec_config
+ exec_setup
+ exec_install
+ else
+ case task
+ when 'config', 'test'
+ ;
+ when 'clean', 'distclean'
+ @config.load_savefile if File.exist?(@config.savefile)
+ else
+ @config.load_savefile
+ end
+ __send__ "parsearg_#{task}"
+ init_installers
+ __send__ "exec_#{task}"
+ end
+ end
+
+ def run_metaconfigs
+ @config.load_script "#{@ardir}/metaconfig"
+ end
+
+ def init_installers
+ @installer = Installer.new(@config, @ardir, File.expand_path('.'))
+ end
+
+ #
+ # Hook Script API bases
+ #
+
+ def srcdir_root
+ @ardir
+ end
+
+ def objdir_root
+ '.'
+ end
+
+ def relpath
+ '.'
+ end
+
+ #
+ # Option Parsing
+ #
+
+ def parsearg_global
+ while arg = ARGV.shift
+ case arg
+ when /\A\w+\z/
+ setup_rb_error "invalid task: #{arg}" unless valid_task?(arg)
+ return arg
+ when '-q', '--quiet'
+ @config.verbose = false
+ when '--verbose'
+ @config.verbose = true
+ when '--help'
+ print_usage $stdout
+ exit 0
+ when '--version'
+ puts "#{File.basename($0)} version #{Version}"
+ exit 0
+ when '--copyright'
+ puts Copyright
+ exit 0
+ else
+ setup_rb_error "unknown global option '#{arg}'"
+ end
+ end
+ nil
+ end
+
+ def valid_task?(t)
+ valid_task_re() =~ t
+ end
+
+ def valid_task_re
+ @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/
+ end
+
+ def parsearg_no_options
+ unless ARGV.empty?
+ task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1)
+ setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}"
+ end
+ end
+
+ alias parsearg_show parsearg_no_options
+ alias parsearg_setup parsearg_no_options
+ alias parsearg_test parsearg_no_options
+ alias parsearg_clean parsearg_no_options
+ alias parsearg_distclean parsearg_no_options
+
+ def parsearg_config
+ evalopt = []
+ set = []
+ @config.config_opt = []
+ while i = ARGV.shift
+ if /\A--?\z/ =~ i
+ @config.config_opt = ARGV.dup
+ break
+ end
+ name, value = *@config.parse_opt(i)
+ if @config.value_config?(name)
+ @config[name] = value
+ else
+ evalopt.push [name, value]
+ end
+ set.push name
+ end
+ evalopt.each do |name, value|
+ @config.lookup(name).evaluate value, @config
+ end
+ # Check if configuration is valid
+ set.each do |n|
+ @config[n] if @config.value_config?(n)
+ end
+ end
+
+ def parsearg_install
+ @config.no_harm = false
+ @config.install_prefix = ''
+ while a = ARGV.shift
+ case a
+ when '--no-harm'
+ @config.no_harm = true
+ when /\A--prefix=/
+ path = a.split(/=/, 2)[1]
+ path = File.expand_path(path) unless path[0,1] == '/'
+ @config.install_prefix = path
+ else
+ setup_rb_error "install: unknown option #{a}"
+ end
+ end
+ end
+
+ def print_usage(out)
+ out.puts 'Typical Installation Procedure:'
+ out.puts " $ ruby #{File.basename $0} config"
+ out.puts " $ ruby #{File.basename $0} setup"
+ out.puts " # ruby #{File.basename $0} install (may require root privilege)"
+ out.puts
+ out.puts 'Detailed Usage:'
+ out.puts " ruby #{File.basename $0} <global option>"
+ out.puts " ruby #{File.basename $0} [<global options>] <task> [<task options>]"
+
+ fmt = " %-24s %s\n"
+ out.puts
+ out.puts 'Global options:'
+ out.printf fmt, '-q,--quiet', 'suppress message outputs'
+ out.printf fmt, ' --verbose', 'output messages verbosely'
+ out.printf fmt, ' --help', 'print this message'
+ out.printf fmt, ' --version', 'print version and quit'
+ out.printf fmt, ' --copyright', 'print copyright and quit'
+ out.puts
+ out.puts 'Tasks:'
+ TASKS.each do |name, desc|
+ out.printf fmt, name, desc
+ end
+
+ fmt = " %-24s %s [%s]\n"
+ out.puts
+ out.puts 'Options for CONFIG or ALL:'
+ @config.each do |item|
+ out.printf fmt, item.help_opt, item.description, item.help_default
+ end
+ out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
+ out.puts
+ out.puts 'Options for INSTALL:'
+ out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
+ out.printf fmt, '--prefix=path', 'install path prefix', ''
+ out.puts
+ end
+
+ #
+ # Task Handlers
+ #
+
+ def exec_config
+ @installer.exec_config
+ @config.save # must be final
+ end
+
+ def exec_setup
+ @installer.exec_setup
+ end
+
+ def exec_install
+ @installer.exec_install
+ end
+
+ def exec_test
+ @installer.exec_test
+ end
+
+ def exec_show
+ @config.each do |i|
+ printf "%-20s %s\n", i.name, i.value if i.value?
+ end
+ end
+
+ def exec_clean
+ @installer.exec_clean
+ end
+
+ def exec_distclean
+ @installer.exec_distclean
+ end
+
+end # class ToplevelInstaller
+
+
+class ToplevelInstallerMulti < ToplevelInstaller
+
+ include FileOperations
+
+ def initialize(ardir_root, config)
+ super
+ @packages = directories_of("#{@ardir}/packages")
+ raise 'no package exists' if @packages.empty?
+ @root_installer = Installer.new(@config, @ardir, File.expand_path('.'))
+ end
+
+ def run_metaconfigs
+ @config.load_script "#{@ardir}/metaconfig", self
+ @packages.each do |name|
+ @config.load_script "#{@ardir}/packages/#{name}/metaconfig"
+ end
+ end
+
+ attr_reader :packages
+
+ def packages=(list)
+ raise 'package list is empty' if list.empty?
+ list.each do |name|
+ raise "directory packages/#{name} does not exist"\
+ unless File.dir?("#{@ardir}/packages/#{name}")
+ end
+ @packages = list
+ end
+
+ def init_installers
+ @installers = {}
+ @packages.each do |pack|
+ @installers[pack] = Installer.new(@config,
+ "#{@ardir}/packages/#{pack}",
+ "packages/#{pack}")
+ end
+ with = extract_selection(config('with'))
+ without = extract_selection(config('without'))
+ @selected = @installers.keys.select {|name|
+ (with.empty? or with.include?(name)) \
+ and not without.include?(name)
+ }
+ end
+
+ def extract_selection(list)
+ a = list.split(/,/)
+ a.each do |name|
+ setup_rb_error "no such package: #{name}" unless @installers.key?(name)
+ end
+ a
+ end
+
+ def print_usage(f)
+ super
+ f.puts 'Inluded packages:'
+ f.puts ' ' + @packages.sort.join(' ')
+ f.puts
+ end
+
+ #
+ # Task Handlers
+ #
+
+ def exec_config
+ run_hook 'pre-config'
+ each_selected_installers {|inst| inst.exec_config }
+ run_hook 'post-config'
+ @config.save # must be final
+ end
+
+ def exec_setup
+ run_hook 'pre-setup'
+ each_selected_installers {|inst| inst.exec_setup }
+ run_hook 'post-setup'
+ end
+
+ def exec_install
+ run_hook 'pre-install'
+ each_selected_installers {|inst| inst.exec_install }
+ run_hook 'post-install'
+ end
+
+ def exec_test
+ run_hook 'pre-test'
+ each_selected_installers {|inst| inst.exec_test }
+ run_hook 'post-test'
+ end
+
+ def exec_clean
+ rm_f @config.savefile
+ run_hook 'pre-clean'
+ each_selected_installers {|inst| inst.exec_clean }
+ run_hook 'post-clean'
+ end
+
+ def exec_distclean
+ rm_f @config.savefile
+ run_hook 'pre-distclean'
+ each_selected_installers {|inst| inst.exec_distclean }
+ run_hook 'post-distclean'
+ end
+
+ #
+ # lib
+ #
+
+ def each_selected_installers
+ Dir.mkdir 'packages' unless File.dir?('packages')
+ @selected.each do |pack|
+ $stderr.puts "Processing the package `#{pack}' ..." if verbose?
+ Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
+ Dir.chdir "packages/#{pack}"
+ yield @installers[pack]
+ Dir.chdir '../..'
+ end
+ end
+
+ def run_hook(id)
+ @root_installer.run_hook id
+ end
+
+ # module FileOperations requires this
+ def verbose?
+ @config.verbose?
+ end
+
+ # module FileOperations requires this
+ def no_harm?
+ @config.no_harm?
+ end
+
+end # class ToplevelInstallerMulti
+
+
+class Installer
+
+ FILETYPES = %w( bin lib ext data conf man )
+
+ include FileOperations
+ include HookScriptAPI
+
+ def initialize(config, srcroot, objroot)
+ @config = config
+ @srcdir = File.expand_path(srcroot)
+ @objdir = File.expand_path(objroot)
+ @currdir = '.'
+ end
+
+ def inspect
+ "#<#{self.class} #{File.basename(@srcdir)}>"
+ end
+
+ def noop(rel)
+ end
+
+ #
+ # Hook Script API base methods
+ #
+
+ def srcdir_root
+ @srcdir
+ end
+
+ def objdir_root
+ @objdir
+ end
+
+ def relpath
+ @currdir
+ end
+
+ #
+ # Config Access
+ #
+
+ # module FileOperations requires this
+ def verbose?
+ @config.verbose?
+ end
+
+ # module FileOperations requires this
+ def no_harm?
+ @config.no_harm?
+ end
+
+ def verbose_off
+ begin
+ save, @config.verbose = @config.verbose?, false
+ yield
+ ensure
+ @config.verbose = save
+ end
+ end
+
+ #
+ # TASK config
+ #
+
+ def exec_config
+ exec_task_traverse 'config'
+ end
+
+ alias config_dir_bin noop
+ alias config_dir_lib noop
+
+ def config_dir_ext(rel)
+ extconf if extdir?(curr_srcdir())
+ end
+
+ alias config_dir_data noop
+ alias config_dir_conf noop
+ alias config_dir_man noop
+
+ def extconf
+ ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt
+ end
+
+ #
+ # TASK setup
+ #
+
+ def exec_setup
+ exec_task_traverse 'setup'
+ end
+
+ def setup_dir_bin(rel)
+ files_of(curr_srcdir()).each do |fname|
+ update_shebang_line "#{curr_srcdir()}/#{fname}"
+ end
+ end
+
+ alias setup_dir_lib noop
+
+ def setup_dir_ext(rel)
+ make if extdir?(curr_srcdir())
+ end
+
+ alias setup_dir_data noop
+ alias setup_dir_conf noop
+ alias setup_dir_man noop
+
+ def update_shebang_line(path)
+ return if no_harm?
+ return if config('shebang') == 'never'
+ old = Shebang.load(path)
+ if old
+ $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1
+ new = new_shebang(old)
+ return if new.to_s == old.to_s
+ else
+ return unless config('shebang') == 'all'
+ new = Shebang.new(config('rubypath'))
+ end
+ $stderr.puts "updating shebang: #{File.basename(path)}" if verbose?
+ open_atomic_writer(path) {|output|
+ File.open(path, 'rb') {|f|
+ f.gets if old # discard
+ output.puts new.to_s
+ output.print f.read
+ }
+ }
+ end
+
+ def new_shebang(old)
+ if /\Aruby/ =~ File.basename(old.cmd)
+ Shebang.new(config('rubypath'), old.args)
+ elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby'
+ Shebang.new(config('rubypath'), old.args[1..-1])
+ else
+ return old unless config('shebang') == 'all'
+ Shebang.new(config('rubypath'))
+ end
+ end
+
+ def open_atomic_writer(path, &block)
+ tmpfile = File.basename(path) + '.tmp'
+ begin
+ File.open(tmpfile, 'wb', &block)
+ File.rename tmpfile, File.basename(path)
+ ensure
+ File.unlink tmpfile if File.exist?(tmpfile)
+ end
+ end
+
+ class Shebang
+ def Shebang.load(path)
+ line = nil
+ File.open(path) {|f|
+ line = f.gets
+ }
+ return nil unless /\A#!/ =~ line
+ parse(line)
+ end
+
+ def Shebang.parse(line)
+ cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ')
+ new(cmd, args)
+ end
+
+ def initialize(cmd, args = [])
+ @cmd = cmd
+ @args = args
+ end
+
+ attr_reader :cmd
+ attr_reader :args
+
+ def to_s
+ "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}")
+ end
+ end
+
+ #
+ # TASK install
+ #
+
+ def exec_install
+ rm_f 'InstalledFiles'
+ exec_task_traverse 'install'
+ end
+
+ def install_dir_bin(rel)
+ install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755
+ end
+
+ def install_dir_lib(rel)
+ install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644
+ end
+
+ def install_dir_ext(rel)
+ return unless extdir?(curr_srcdir())
+ install_files rubyextentions('.'),
+ "#{config('sodir')}/#{File.dirname(rel)}",
+ 0555
+ end
+
+ def install_dir_data(rel)
+ install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644
+ end
+
+ def install_dir_conf(rel)
+ # FIXME: should not remove current config files
+ # (rename previous file to .old/.org)
+ install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644
+ end
+
+ def install_dir_man(rel)
+ install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644
+ end
+
+ def install_files(list, dest, mode)
+ mkdir_p dest, @config.install_prefix
+ list.each do |fname|
+ install fname, dest, mode, @config.install_prefix
+ end
+ end
+
+ def libfiles
+ glob_reject(%w(*.y *.output), targetfiles())
+ end
+
+ def rubyextentions(dir)
+ ents = glob_select("*.#{@config.dllext}", targetfiles())
+ if ents.empty?
+ setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
+ end
+ ents
+ end
+
+ def targetfiles
+ mapdir(existfiles() - hookfiles())
+ end
+
+ def mapdir(ents)
+ ents.map {|ent|
+ if File.exist?(ent)
+ then ent # objdir
+ else "#{curr_srcdir()}/#{ent}" # srcdir
+ end
+ }
+ end
+
+ # picked up many entries from cvs-1.11.1/src/ignore.c
+ JUNK_FILES = %w(
+ core RCSLOG tags TAGS .make.state
+ .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
+ *~ *.old *.bak *.BAK *.orig *.rej _$* *$
+
+ *.org *.in .*
+ )
+
+ def existfiles
+ glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.')))
+ end
+
+ def hookfiles
+ %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
+ %w( config setup install clean ).map {|t| sprintf(fmt, t) }
+ }.flatten
+ end
+
+ def glob_select(pat, ents)
+ re = globs2re([pat])
+ ents.select {|ent| re =~ ent }
+ end
+
+ def glob_reject(pats, ents)
+ re = globs2re(pats)
+ ents.reject {|ent| re =~ ent }
+ end
+
+ GLOB2REGEX = {
+ '.' => '\.',
+ '$' => '\$',
+ '#' => '\#',
+ '*' => '.*'
+ }
+
+ def globs2re(pats)
+ /\A(?:#{
+ pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|')
+ })\z/
+ end
+
+ #
+ # TASK test
+ #
+
+ TESTDIR = 'test'
+
+ def exec_test
+ unless File.directory?('test')
+ $stderr.puts 'no test in this package' if verbose?
+ return
+ end
+ $stderr.puts 'Running tests...' if verbose?
+ begin
+ require 'test/unit'
+ rescue LoadError
+ setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.'
+ end
+ runner = Test::Unit::AutoRunner.new(true)
+ runner.to_run << TESTDIR
+ runner.run
+ end
+
+ #
+ # TASK clean
+ #
+
+ def exec_clean
+ exec_task_traverse 'clean'
+ rm_f @config.savefile
+ rm_f 'InstalledFiles'
+ end
+
+ alias clean_dir_bin noop
+ alias clean_dir_lib noop
+ alias clean_dir_data noop
+ alias clean_dir_conf noop
+ alias clean_dir_man noop
+
+ def clean_dir_ext(rel)
+ return unless extdir?(curr_srcdir())
+ make 'clean' if File.file?('Makefile')
+ end
+
+ #
+ # TASK distclean
+ #
+
+ def exec_distclean
+ exec_task_traverse 'distclean'
+ rm_f @config.savefile
+ rm_f 'InstalledFiles'
+ end
+
+ alias distclean_dir_bin noop
+ alias distclean_dir_lib noop
+
+ def distclean_dir_ext(rel)
+ return unless extdir?(curr_srcdir())
+ make 'distclean' if File.file?('Makefile')
+ end
+
+ alias distclean_dir_data noop
+ alias distclean_dir_conf noop
+ alias distclean_dir_man noop
+
+ #
+ # Traversing
+ #
+
+ def exec_task_traverse(task)
+ run_hook "pre-#{task}"
+ FILETYPES.each do |type|
+ if type == 'ext' and config('without-ext') == 'yes'
+ $stderr.puts 'skipping ext/* by user option' if verbose?
+ next
+ end
+ traverse task, type, "#{task}_dir_#{type}"
+ end
+ run_hook "post-#{task}"
+ end
+
+ def traverse(task, rel, mid)
+ dive_into(rel) {
+ run_hook "pre-#{task}"
+ __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
+ directories_of(curr_srcdir()).each do |d|
+ traverse task, "#{rel}/#{d}", mid
+ end
+ run_hook "post-#{task}"
+ }
+ end
+
+ def dive_into(rel)
+ return unless File.dir?("#{@srcdir}/#{rel}")
+
+ dir = File.basename(rel)
+ Dir.mkdir dir unless File.dir?(dir)
+ prevdir = Dir.pwd
+ Dir.chdir dir
+ $stderr.puts '---> ' + rel if verbose?
+ @currdir = rel
+ yield
+ Dir.chdir prevdir
+ $stderr.puts '<--- ' + rel if verbose?
+ @currdir = File.dirname(rel)
+ end
+
+ def run_hook(id)
+ path = [ "#{curr_srcdir()}/#{id}",
+ "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) }
+ return unless path
+ begin
+ instance_eval File.read(path), path, 1
+ rescue
+ raise if $DEBUG
+ setup_rb_error "hook #{path} failed:\n" + $!.message
+ end
+ end
+
+end # class Installer
+
+
+class SetupError < StandardError; end
+
+def setup_rb_error(msg)
+ raise SetupError, msg
+end
+
+if $0 == __FILE__
+ begin
+ ToplevelInstaller.invoke
+ rescue SetupError
+ raise if $DEBUG
+ $stderr.puts $!.message
+ $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
+ exit 1
+ end
+end
diff --git a/lib/rb/spec/socket_spec_shared.rb b/lib/rb/spec/socket_spec_shared.rb
index a0092e1..b32ab44 100644
--- a/lib/rb/spec/socket_spec_shared.rb
+++ b/lib/rb/spec/socket_spec_shared.rb
@@ -26,19 +26,13 @@
it "should raise an error when it cannot read from the handle" do
@socket.open
- @handle.should_receive(:read).with(17).and_raise(StandardError)
+ @handle.should_receive(:readpartial).with(17).and_raise(StandardError)
lambda { @socket.read(17) }.should raise_error(Thrift::TransportException) { |e| e.type.should == Thrift::TransportException::NOT_OPEN }
end
- it "should raise an error when it reads no data from the handle" do
- @socket.open
- @handle.should_receive(:read).with(17).and_return("")
- lambda { @socket.read(17) }.should raise_error(Thrift::TransportException, "Socket: Could not read 17 bytes from #{@socket.instance_variable_get("@desc")}")
- end
-
it "should return the data read when reading from the handle works" do
@socket.open
- @handle.should_receive(:read).with(17).and_return("test data")
+ @handle.should_receive(:readpartial).with(17).and_return("test data")
@socket.read(17).should == "test data"
end
diff --git a/lib/rb/spec/transport_spec.rb b/lib/rb/spec/transport_spec.rb
index 44d0508..1fe8600 100644
--- a/lib/rb/spec/transport_spec.rb
+++ b/lib/rb/spec/transport_spec.rb
@@ -48,18 +48,26 @@
end
describe BufferedTransport do
- it "should pass through everything but write/flush" do
+ it "should pass through everything but write/flush/read" do
trans = mock("Transport")
trans.should_receive(:open?).ordered.and_return("+ open?")
trans.should_receive(:open).ordered.and_return("+ open")
trans.should_receive(:flush).ordered # from the close
trans.should_receive(:close).ordered.and_return("+ close")
- trans.should_receive(:read).with(217).ordered.and_return("+ read")
btrans = BufferedTransport.new(trans)
btrans.open?.should == "+ open?"
btrans.open.should == "+ open"
btrans.close.should == "+ close"
- btrans.read(217).should == "+ read"
+ end
+
+ it "should buffer reads in chunks of #{BufferedTransport::DEFAULT_BUFFER}" do
+ trans = mock("Transport")
+ trans.should_receive(:read).with(BufferedTransport::DEFAULT_BUFFER).and_return("lorum ipsum dolor emet")
+ btrans = BufferedTransport.new(trans)
+ btrans.read(6).should == "lorum "
+ btrans.read(6).should == "ipsum "
+ btrans.read(6).should == "dolor "
+ btrans.read(6).should == "emet"
end
it "should buffer writes and send them on flush" do
diff --git a/test/Makefile.am b/test/Makefile.am
index 47e6b76..bac5b06 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -8,6 +8,10 @@
SUBDIRS += py
endif
+if ENABLE_RUBY
+SUBDIRS += rb
+endif
+
noinst_LTLIBRARIES = libtestgencpp.la
libtestgencpp_la_SOURCES = \
gen-cpp/DebugProtoTest_types.cpp \
diff --git a/test/SmallTest.thrift b/test/SmallTest.thrift
index 36ee349..dd5634d 100644
--- a/test/SmallTest.thrift
+++ b/test/SmallTest.thrift
@@ -11,6 +11,10 @@
"ASDFLJASDF"
}
+struct BoolPasser {
+ 1: bool value = 1
+}
+
struct Hello {
1: i32 simple = 53,
2: map<i32,i32> complex = {23:532, 6243:632, 2355:532},
diff --git a/test/rb/Makefile b/test/rb/Makefile
deleted file mode 100644
index 5c1e61a..0000000
--- a/test/rb/Makefile
+++ /dev/null
@@ -1,22 +0,0 @@
-# Makefile for Thrift test project.
-#
-# Author:
-# Mark Slee <mcslee@facebook.com>
-
-# Default target is everything
-target: all
-
-# Tools
-THRIFT = ../../compiler/cpp/thrift
-
-all: stubs check
-
-stubs: ../ThriftTest.thrift ../SmallTest.thrift
- $(THRIFT) --gen rb ../ThriftTest.thrift
- $(THRIFT) --gen rb ../SmallTest.thrift
-
-check: stubs
- ruby test_suite.rb
-
-clean:
- $(RM) -r gen-rb
diff --git a/test/rb/Makefile.am b/test/rb/Makefile.am
new file mode 100644
index 0000000..b19fcde
--- /dev/null
+++ b/test/rb/Makefile.am
@@ -0,0 +1,9 @@
+THRIFT = $(top_srcdir)/compiler/cpp/thrift
+
+stubs: ../ThriftTest.thrift ../SmallTest.thrift
+ $(THRIFT) --gen rb ../ThriftTest.thrift
+ $(THRIFT) --gen rb ../SmallTest.thrift
+
+check: stubs
+ $(RUBY) test_suite.rb
+
diff --git a/test/rb/benchmarks/protocol_benchmark.rb b/test/rb/benchmarks/protocol_benchmark.rb
new file mode 100644
index 0000000..99f0760
--- /dev/null
+++ b/test/rb/benchmarks/protocol_benchmark.rb
@@ -0,0 +1,158 @@
+$LOAD_PATH.unshift File.join(File.dirname(__FILE__), *%w[.. .. .. lib rb lib])
+$LOAD_PATH.unshift File.join(File.dirname(__FILE__), *%w[.. .. .. lib rb ext])
+
+require 'thrift'
+require 'thrift/transport'
+require 'thrift/protocol/binaryprotocol'
+require 'thrift/protocol/binaryprotocolaccelerated'
+
+require 'benchmark'
+require 'rubygems'
+require 'set'
+require 'pp'
+
+# require 'ruby-debug'
+# require 'ruby-prof'
+
+require File.join(File.dirname(__FILE__), '../fixtures/structs')
+
+transport1 = Thrift::MemoryBuffer.new
+ruby_binary_protocol = Thrift::BinaryProtocol.new(transport1)
+
+transport2 = Thrift::MemoryBuffer.new
+c_fast_binary_protocol = Thrift::BinaryProtocolAccelerated.new(transport2)
+
+
+ooe = Fixtures::Structs::OneOfEach.new
+ooe.im_true = true
+ooe.im_false = false
+ooe.a_bite = -42
+ooe.integer16 = 27000
+ooe.integer32 = 1<<24
+ooe.integer64 = 6000 * 1000 * 1000
+ooe.double_precision = Math::PI
+ooe.some_characters = "Debug THIS!"
+ooe.zomg_unicode = "\xd7\n\a\t"
+
+n1 = Fixtures::Structs::Nested1.new
+n1.a_list = []
+n1.a_list << ooe << ooe << ooe << ooe
+n1.i32_map = {}
+n1.i32_map[1234] = ooe
+n1.i32_map[46345] = ooe
+n1.i32_map[-34264] = ooe
+n1.i64_map = {}
+n1.i64_map[43534986783945] = ooe
+n1.i64_map[-32434639875122] = ooe
+n1.dbl_map = {}
+n1.dbl_map[324.65469834] = ooe
+n1.dbl_map[-9458672340.4986798345112] = ooe
+n1.str_map = {}
+n1.str_map['sdoperuix'] = ooe
+n1.str_map['pwoerxclmn'] = ooe
+
+n2 = Fixtures::Structs::Nested2.new
+n2.a_list = []
+n2.a_list << n1 << n1 << n1 << n1 << n1
+n2.i32_map = {}
+n2.i32_map[398345] = n1
+n2.i32_map[-2345] = n1
+n2.i32_map[12312] = n1
+n2.i64_map = {}
+n2.i64_map[2349843765934] = n1
+n2.i64_map[-123234985495] = n1
+n2.i64_map[0] = n1
+n2.dbl_map = {}
+n2.dbl_map[23345345.38927834] = n1
+n2.dbl_map[-1232349.5489345] = n1
+n2.dbl_map[-234984574.23498725] = n1
+n2.str_map = {}
+n2.str_map[''] = n1
+n2.str_map['sdflkertpioux'] = n1
+n2.str_map['sdfwepwdcjpoi'] = n1
+
+n3 = Fixtures::Structs::Nested3.new
+n3.a_list = []
+n3.a_list << n2 << n2 << n2 << n2 << n2
+n3.i32_map = {}
+n3.i32_map[398345] = n2
+n3.i32_map[-2345] = n2
+n3.i32_map[12312] = n2
+n3.i64_map = {}
+n3.i64_map[2349843765934] = n2
+n3.i64_map[-123234985495] = n2
+n3.i64_map[0] = n2
+n3.dbl_map = {}
+n3.dbl_map[23345345.38927834] = n2
+n3.dbl_map[-1232349.5489345] = n2
+n3.dbl_map[-234984574.23498725] = n2
+n3.str_map = {}
+n3.str_map[''] = n2
+n3.str_map['sdflkertpioux'] = n2
+n3.str_map['sdfwepwdcjpoi'] = n2
+
+n4 = Fixtures::Structs::Nested4.new
+n4.a_list = []
+n4.a_list << n3
+n4.i32_map = {}
+n4.i32_map[-2345] = n3
+n4.i64_map = {}
+n4.i64_map[2349843765934] = n3
+n4.dbl_map = {}
+n4.dbl_map[-1232349.5489345] = n3
+n4.str_map = {}
+n4.str_map[''] = n3
+
+
+# prof = RubyProf.profile do
+# n4.write(c_fast_binary_protocol)
+# Fixtures::Structs::Nested4.new.read(c_fast_binary_protocol)
+# end
+#
+# printer = RubyProf::GraphHtmlPrinter.new(prof)
+# printer.print(STDOUT, :min_percent=>0)
+
+Benchmark.bmbm do |x|
+ x.report("ruby write large (1MB) structure once") do
+ n4.write(ruby_binary_protocol)
+ end
+
+ x.report("ruby read large (1MB) structure once") do
+ Fixtures::Structs::Nested4.new.read(ruby_binary_protocol)
+ end
+
+ x.report("c write large (1MB) structure once") do
+ n4.write(c_fast_binary_protocol)
+ end
+
+ x.report("c read large (1MB) structure once") do
+ Fixtures::Structs::Nested4.new.read(c_fast_binary_protocol)
+ end
+
+
+
+ x.report("ruby write 10_000 small structures") do
+ 10_000.times do
+ ooe.write(ruby_binary_protocol)
+ end
+ end
+
+ x.report("ruby read 10_000 small structures") do
+ 10_000.times do
+ Fixtures::Structs::OneOfEach.new.read(ruby_binary_protocol)
+ end
+ end
+
+ x.report("c write 10_000 small structures") do
+ 10_000.times do
+ ooe.write(c_fast_binary_protocol)
+ end
+ end
+
+ x.report("c read 10_000 small structures") do
+ 10_000.times do
+ Fixtures::Structs::OneOfEach.new.read(c_fast_binary_protocol)
+ end
+ end
+
+end
diff --git a/test/rb/core/test_binary_protocol_accelerated.rb b/test/rb/core/test_binary_protocol_accelerated.rb
new file mode 100644
index 0000000..15c033e
--- /dev/null
+++ b/test/rb/core/test_binary_protocol_accelerated.rb
@@ -0,0 +1,275 @@
+require File.join(File.dirname(__FILE__), '../test_helper')
+require File.join(File.dirname(__FILE__), '../fixtures/structs')
+
+require 'thrift'
+require 'thrift/transport'
+require 'thrift/protocol/binaryprotocol'
+require 'thrift/protocol/binaryprotocolaccelerated'
+
+class BinaryProtocolAcceleratedTest < Test::Unit::TestCase
+ I8_MIN = -128
+ I8_MAX = 127
+ I16_MIN = -32768
+ I16_MAX = 32767
+ I32_MIN = -2147483648
+ I32_MAX = 2147483647
+ I64_MIN = -9223372036854775808
+ I64_MAX = 9223372036854775807
+ DBL_MIN = Float::MIN
+ DBL_MAX = Float::MAX
+
+ # booleans might be read back differently, so we supply a list [write_value, read_value]
+ BOOL_VALUES = [[0,true], [14,true], [-14,true], [true,true], [false,false], ["",true]]
+ BYTE_VALUES = [14, -14, I8_MIN, I8_MAX]
+ I16_VALUES = [400, 0, -234, I16_MIN, I16_MAX]
+ I32_VALUES = [325, 0, -1, -1073741825, -278, -4352388, I32_MIN, I32_MAX]
+ I64_VALUES = [15, 0, -33, I64_MIN, I64_MAX]
+ DBL_VALUES = [DBL_MIN, -33.8755, 0, 3658.1279, DBL_MAX]
+ STR_VALUES = ["", "welcome to my test"]
+
+ def setup
+ @trans = Thrift::MemoryBuffer.new
+ @fast_proto = Thrift::BinaryProtocolAccelerated.new(@trans)
+ @slow_proto = Thrift::BinaryProtocol.new(@trans)
+ end
+
+ def assert_encodes_struct(obj)
+ obj.write(@slow_proto)
+ expected = @trans.read(@trans.available) # read it all baby
+ assert_equal expected, @fast_proto.encode_binary(obj)
+ end
+
+ # Assumes encode works
+ def assert_decodes_struct(obj, eql_obj = nil)
+ data = @fast_proto.encode_binary(obj)
+ @trans.write data
+ assert_equal (eql_obj || obj), @fast_proto.decode_binary(obj.class.new, @trans)
+ end
+
+ def test_encodes_and_decodes_bools
+ BOOL_VALUES.each do |(write_val, read_val)|
+ obj = Fixtures::Structs::OneBool.new(:bool => write_val)
+ assert_encodes_struct obj
+ assert_decodes_struct obj, Fixtures::Structs::OneBool.new(:bool => read_val)
+ end
+ end
+
+ def test_encodes_and_decodes_bytes
+ BYTE_VALUES.each do |val|
+ obj = Fixtures::Structs::OneByte.new(:byte => val)
+ assert_encodes_struct obj
+ assert_decodes_struct obj
+ end
+ end
+
+ def test_encodes_and_decodes_i16
+ I16_VALUES.each do |val|
+ obj = Fixtures::Structs::OneI16.new(:i16 => val)
+ assert_encodes_struct obj
+ assert_decodes_struct obj
+ end
+ end
+
+ def test_encodes_and_decodes_i32
+ I32_VALUES.each do |val|
+ obj = Fixtures::Structs::OneI32.new(:i32 => val)
+ assert_encodes_struct obj
+ assert_decodes_struct obj
+ end
+ end
+
+ def test_encodes_and_decodes_i64
+ I64_VALUES.each do |val|
+ obj = Fixtures::Structs::OneI64.new(:i64 => val)
+ assert_encodes_struct obj
+ assert_decodes_struct obj
+ end
+ end
+
+ def test_encodes_and_decodes_double
+ DBL_VALUES.each do |val|
+ obj = Fixtures::Structs::OneDouble.new(:double => val)
+ assert_encodes_struct obj
+ assert_decodes_struct obj
+ end
+ end
+
+ def test_encodes_strings
+ STR_VALUES.each do |val|
+ obj = Fixtures::Structs::OneString.new(:string => val)
+ assert_encodes_struct obj
+ assert_decodes_struct obj
+ end
+ end
+
+ def test_encodes_maps
+ obj = Fixtures::Structs::OneMap.new(:map => {"a" => "b", "c" => "d"})
+ assert_encodes_struct obj
+ assert_decodes_struct obj
+ end
+
+ def test_encodes_lists
+ obj = Fixtures::Structs::OneList.new(:list => ["a", "b", "c", "d"])
+ assert_encodes_struct obj
+ assert_decodes_struct obj
+ end
+
+ def test_encodes_sets
+ expected_return = Fixtures::Structs::OneSet.new(:set => Set.new(["a", "b", "c"]))
+ # support hashes
+ obj = Fixtures::Structs::OneSet.new(:set => {"a" => true, "b" => true, "c" => true})
+ assert_encodes_struct obj
+ assert_decodes_struct obj, expected_return
+
+ # We also support arrays as compatability bug from TBinaryProtocol....
+ obj = Fixtures::Structs::OneSet.new(:set => ["a", "b", "c"])
+ assert_encodes_struct obj
+ assert_decodes_struct obj, expected_return
+
+ # We also support sets
+ obj = Fixtures::Structs::OneSet.new(:set => Set.new(["a", "b", "c"]))
+ assert_encodes_struct obj
+ assert_decodes_struct obj, expected_return
+ end
+
+ def test_encodes_maps_of_maps
+ obj = Fixtures::Structs::NestedMap.new(:map => { 1 => { 2 => 3 }})
+ assert_encodes_struct obj
+ assert_decodes_struct obj
+ end
+
+ def test_encodes_list_of_lists
+ obj = Fixtures::Structs::NestedList.new(:list => [[1,2,3], [4,5,6]])
+ assert_encodes_struct obj
+ assert_decodes_struct obj
+ end
+
+ def test_encodes_set_of_sets
+ obj = Fixtures::Structs::NestedSet.new(:set => Set.new([Set.new('a')]))
+ assert_encodes_struct obj
+
+ # Nested hashes won't work with ==, so we do it by hand
+ data = @fast_proto.encode_binary(obj)
+ @trans.write data
+ decoded = @fast_proto.decode_binary(obj.class.new, @trans)
+ assert_equal decoded.set.entries, Set.new([Set.new('a')]).entries
+ end
+
+ if ENV['MEM_TEST']
+ def test_for_memory_leaks_on_exceptions
+ ooe = Fixtures::Structs::OneOfEach.new
+ ooe.im_true = true
+ ooe.im_false = false
+ ooe.a_bite = -42
+ ooe.integer16 = 27000
+ ooe.integer32 = 1<<24
+ ooe.integer64 = 6000 * 1000 * 1000
+ ooe.double_precision = Math::PI
+ ooe.some_characters = "Debug THIS!"
+ ooe.zomg_unicode = "\xd7\n\a\t"
+
+ 10_000.times do
+ data = @fast_proto.encode_binary(ooe)
+ bytes = []
+ data.each_byte do |b|
+ bytes << b
+ transport = TMemoryBuffer.new
+ transport.write(bytes.pack("c#{bytes.length}"))
+
+ begin
+ @fast_proto.decode_binary(Fixtures::Structs::OneOfEach.new, transport)
+ rescue Exception
+ end
+ end
+ end
+
+ end
+ end
+
+ unless ENV['FAST_TEST']
+ def test_encodes_and_decodes_struct_of_structs
+ ooe = Fixtures::Structs::OneOfEach.new
+ ooe.im_true = true
+ ooe.im_false = false
+ ooe.a_bite = -42
+ ooe.integer16 = 27000
+ ooe.integer32 = 1<<24
+ ooe.integer64 = 6000 * 1000 * 1000
+ ooe.double_precision = Math::PI
+ ooe.some_characters = "Debug THIS!"
+ ooe.zomg_unicode = "\xd7\n\a\t"
+
+ n1 = Fixtures::Structs::Nested1.new
+ n1.a_list = []
+ n1.a_list << ooe << ooe << ooe << ooe
+ n1.i32_map = {}
+ n1.i32_map[1234] = ooe
+ n1.i32_map[46345] = ooe
+ n1.i32_map[-34264] = ooe
+ n1.i64_map = {}
+ n1.i64_map[43534986783945] = ooe
+ n1.i64_map[-32434639875122] = ooe
+ n1.dbl_map = {}
+ n1.dbl_map[324.65469834] = ooe
+ n1.dbl_map[-9458672340.4986798345112] = ooe
+ n1.str_map = {}
+ n1.str_map['sdoperuix'] = ooe
+ n1.str_map['pwoerxclmn'] = ooe
+
+ n2 = Fixtures::Structs::Nested2.new
+ n2.a_list = []
+ n2.a_list << n1 << n1 << n1 << n1 << n1
+ n2.i32_map = {}
+ n2.i32_map[398345] = n1
+ n2.i32_map[-2345] = n1
+ n2.i32_map[12312] = n1
+ n2.i64_map = {}
+ n2.i64_map[2349843765934] = n1
+ n2.i64_map[-123234985495] = n1
+ n2.i64_map[0] = n1
+ n2.dbl_map = {}
+ n2.dbl_map[23345345.38927834] = n1
+ n2.dbl_map[-1232349.5489345] = n1
+ n2.dbl_map[-234984574.23498725] = n1
+ n2.str_map = {}
+ n2.str_map[''] = n1
+ n2.str_map['sdflkertpioux'] = n1
+ n2.str_map['sdfwepwdcjpoi'] = n1
+
+ n3 = Fixtures::Structs::Nested3.new
+ n3.a_list = []
+ n3.a_list << n2 << n2 << n2 << n2 << n2
+ n3.i32_map = {}
+ n3.i32_map[398345] = n2
+ n3.i32_map[-2345] = n2
+ n3.i32_map[12312] = n2
+ n3.i64_map = {}
+ n3.i64_map[2349843765934] = n2
+ n3.i64_map[-123234985495] = n2
+ n3.i64_map[0] = n2
+ n3.dbl_map = {}
+ n3.dbl_map[23345345.38927834] = n2
+ n3.dbl_map[-1232349.5489345] = n2
+ n3.dbl_map[-234984574.23498725] = n2
+ n3.str_map = {}
+ n3.str_map[''] = n2
+ n3.str_map['sdflkertpioux'] = n2
+ n3.str_map['sdfwepwdcjpoi'] = n2
+
+ n4 = Fixtures::Structs::Nested4.new
+ n4.a_list = []
+ n4.a_list << n3
+ n4.i32_map = {}
+ n4.i32_map[-2345] = n3
+ n4.i64_map = {}
+ n4.i64_map[2349843765934] = n3
+ n4.dbl_map = {}
+ n4.dbl_map[-1232349.5489345] = n3
+ n4.str_map = {}
+ n4.str_map[''] = n3
+
+ assert_encodes_struct n4
+ assert_decodes_struct n4
+ end
+ end
+end
diff --git a/test/rb/fixtures/structs.rb b/test/rb/fixtures/structs.rb
new file mode 100644
index 0000000..82c291e
--- /dev/null
+++ b/test/rb/fixtures/structs.rb
@@ -0,0 +1,225 @@
+require 'thrift'
+
+module Fixtures
+ module Structs
+ class OneBool
+ include Thrift::Struct
+ attr_accessor :bool
+ FIELDS = {
+ 1 => {:type => Thrift::Types::BOOL, :name => 'bool'}
+ }
+ end
+
+ class OneByte
+ include Thrift::Struct
+ attr_accessor :byte
+ FIELDS = {
+ 1 => {:type => Thrift::Types::BYTE, :name => 'byte'}
+ }
+ end
+
+ class OneI16
+ include Thrift::Struct
+ attr_accessor :i16
+ FIELDS = {
+ 1 => {:type => Thrift::Types::I16, :name => 'i16'}
+ }
+ end
+
+ class OneI32
+ include Thrift::Struct
+ attr_accessor :i32
+ FIELDS = {
+ 1 => {:type => Thrift::Types::I32, :name => 'i32'}
+ }
+ end
+
+ class OneI64
+ include Thrift::Struct
+ attr_accessor :i64
+ FIELDS = {
+ 1 => {:type => Thrift::Types::I64, :name => 'i64'}
+ }
+ end
+
+ class OneDouble
+ include Thrift::Struct
+ attr_accessor :double
+ FIELDS = {
+ 1 => {:type => Thrift::Types::DOUBLE, :name => 'double'}
+ }
+ end
+
+ class OneString
+ include Thrift::Struct
+ attr_accessor :string
+ FIELDS = {
+ 1 => {:type => Thrift::Types::STRING, :name => 'string'}
+ }
+ end
+
+ class OneMap
+ include Thrift::Struct
+ attr_accessor :map
+ FIELDS = {
+ 1 => {:type => Thrift::Types::MAP, :name => 'map', :key => {:type => Thrift::Types::STRING}, :value => {:type => Thrift::Types::STRING}}
+ }
+ end
+
+ class NestedMap
+ include Thrift::Struct
+ attr_accessor :map
+ FIELDS = {
+ 0 => {:type => Thrift::Types::MAP, :name => 'map', :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::MAP, :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::I32}}}
+ }
+ end
+
+ class OneList
+ include Thrift::Struct
+ attr_accessor :list
+ FIELDS = {
+ 1 => {:type => Thrift::Types::LIST, :name => 'list', :element => {:type => Thrift::Types::STRING}}
+ }
+ end
+
+ class NestedList
+ include Thrift::Struct
+ attr_accessor :list
+ FIELDS = {
+ 0 => {:type => Thrift::Types::LIST, :name => 'list', :element => {:type => Thrift::Types::LIST, :element => { :type => Thrift::Types::I32 } } }
+ }
+ end
+
+ class OneSet
+ include Thrift::Struct
+ attr_accessor :set
+ FIELDS = {
+ 1 => {:type => Thrift::Types::SET, :name => 'set', :element => {:type => Thrift::Types::STRING}}
+ }
+ end
+
+ class NestedSet
+ include Thrift::Struct
+ attr_accessor :set
+ FIELDS = {
+ 1 => {:type => Thrift::Types::SET, :name => 'set', :element => {:type => Thrift::Types::SET, :element => { :type => Thrift::Types::STRING } }}
+ }
+ end
+
+ # struct OneOfEach {
+ # 1: bool im_true,
+ # 2: bool im_false,
+ # 3: byte a_bite,
+ # 4: i16 integer16,
+ # 5: i32 integer32,
+ # 6: i64 integer64,
+ # 7: double double_precision,
+ # 8: string some_characters,
+ # 9: string zomg_unicode,
+ # 10: bool what_who,
+ # 11: binary base64,
+ # }
+ class OneOfEach
+ include Thrift::Struct
+ attr_accessor :im_true, :im_false, :a_bite, :integer16, :integer32, :integer64, :double_precision, :some_characters, :zomg_unicode, :what_who, :base64
+ FIELDS = {
+ 1 => {:type => Thrift::Types::BOOL, :name => 'im_true'},
+ 2 => {:type => Thrift::Types::BOOL, :name => 'im_false'},
+ 3 => {:type => Thrift::Types::BYTE, :name => 'a_bite'},
+ 4 => {:type => Thrift::Types::I16, :name => 'integer16'},
+ 5 => {:type => Thrift::Types::I32, :name => 'integer32'},
+ 6 => {:type => Thrift::Types::I64, :name => 'integer64'},
+ 7 => {:type => Thrift::Types::DOUBLE, :name => 'double_precision'},
+ 8 => {:type => Thrift::Types::STRING, :name => 'some_characters'},
+ 9 => {:type => Thrift::Types::STRING, :name => 'zomg_unicode'},
+ 10 => {:type => Thrift::Types::BOOL, :name => 'what_who'},
+ 11 => {:type => Thrift::Types::STRING, :name => 'base64'}
+ }
+
+ # Added for assert_equal
+ def ==(other)
+ [:im_true, :im_false, :a_bite, :integer16, :integer32, :integer64, :double_precision, :some_characters, :zomg_unicode, :what_who, :base64].each do |f|
+ var = "@#{f}"
+ return false if instance_variable_get(var) != other.instance_variable_get(var)
+ end
+ true
+ end
+ end
+
+ # struct Nested1 {
+ # 1: list<OneOfEach> a_list
+ # 2: map<i32, OneOfEach> i32_map
+ # 3: map<i64, OneOfEach> i64_map
+ # 4: map<double, OneOfEach> dbl_map
+ # 5: map<string, OneOfEach> str_map
+ # }
+ class Nested1
+ include Thrift::Struct
+ attr_accessor :a_list, :i32_map, :i64_map, :dbl_map, :str_map
+ FIELDS = {
+ 1 => {:type => Thrift::Types::LIST, :name => 'a_list', :element => {:type => Thrift::Types::STRUCT, :class => OneOfEach}},
+ 2 => {:type => Thrift::Types::MAP, :name => 'i32_map', :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::STRUCT, :class => OneOfEach}},
+ 3 => {:type => Thrift::Types::MAP, :name => 'i64_map', :key => {:type => Thrift::Types::I64}, :value => {:type => Thrift::Types::STRUCT, :class => OneOfEach}},
+ 4 => {:type => Thrift::Types::MAP, :name => 'dbl_map', :key => {:type => Thrift::Types::DOUBLE}, :value => {:type => Thrift::Types::STRUCT, :class => OneOfEach}},
+ 5 => {:type => Thrift::Types::MAP, :name => 'str_map', :key => {:type => Thrift::Types::STRING}, :value => {:type => Thrift::Types::STRUCT, :class => OneOfEach}}
+ }
+ end
+
+ # struct Nested2 {
+ # 1: list<Nested1> a_list
+ # 2: map<i32, Nested1> i32_map
+ # 3: map<i64, Nested1> i64_map
+ # 4: map<double, Nested1> dbl_map
+ # 5: map<string, Nested1> str_map
+ # }
+ class Nested2
+ include Thrift::Struct
+ attr_accessor :a_list, :i32_map, :i64_map, :dbl_map, :str_map
+ FIELDS = {
+ 1 => {:type => Thrift::Types::LIST, :name => 'a_list', :element => {:type => Thrift::Types::STRUCT, :class => Nested1}},
+ 2 => {:type => Thrift::Types::MAP, :name => 'i32_map', :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::STRUCT, :class => Nested1}},
+ 3 => {:type => Thrift::Types::MAP, :name => 'i64_map', :key => {:type => Thrift::Types::I64}, :value => {:type => Thrift::Types::STRUCT, :class => Nested1}},
+ 4 => {:type => Thrift::Types::MAP, :name => 'dbl_map', :key => {:type => Thrift::Types::DOUBLE}, :value => {:type => Thrift::Types::STRUCT, :class => Nested1}},
+ 5 => {:type => Thrift::Types::MAP, :name => 'str_map', :key => {:type => Thrift::Types::STRING}, :value => {:type => Thrift::Types::STRUCT, :class => Nested1}}
+ }
+ end
+
+ # struct Nested3 {
+ # 1: list<Nested2> a_list
+ # 2: map<i32, Nested2> i32_map
+ # 3: map<i64, Nested2> i64_map
+ # 4: map<double, Nested2> dbl_map
+ # 5: map<string, Nested2> str_map
+ # }
+ class Nested3
+ include Thrift::Struct
+ attr_accessor :a_list, :i32_map, :i64_map, :dbl_map, :str_map
+ FIELDS = {
+ 1 => {:type => Thrift::Types::LIST, :name => 'a_list', :element => {:type => Thrift::Types::STRUCT, :class => Nested2}},
+ 2 => {:type => Thrift::Types::MAP, :name => 'i32_map', :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::STRUCT, :class => Nested2}},
+ 3 => {:type => Thrift::Types::MAP, :name => 'i64_map', :key => {:type => Thrift::Types::I64}, :value => {:type => Thrift::Types::STRUCT, :class => Nested2}},
+ 4 => {:type => Thrift::Types::MAP, :name => 'dbl_map', :key => {:type => Thrift::Types::DOUBLE}, :value => {:type => Thrift::Types::STRUCT, :class => Nested2}},
+ 5 => {:type => Thrift::Types::MAP, :name => 'str_map', :key => {:type => Thrift::Types::STRING}, :value => {:type => Thrift::Types::STRUCT, :class => Nested2}}
+ }
+ end
+
+ # struct Nested4 {
+ # 1: list<Nested3> a_list
+ # 2: map<i32, Nested3> i32_map
+ # 3: map<i64, Nested3> i64_map
+ # 4: map<double, Nested3> dbl_map
+ # 5: map<string, Nested3> str_map
+ # }
+ class Nested4
+ include Thrift::Struct
+ attr_accessor :a_list, :i32_map, :i64_map, :dbl_map, :str_map
+ FIELDS = {
+ 1 => {:type => Thrift::Types::LIST, :name => 'a_list', :element => {:type => Thrift::Types::STRUCT, :class => Nested3}},
+ 2 => {:type => Thrift::Types::MAP, :name => 'i32_map', :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::STRUCT, :class => Nested3}},
+ 3 => {:type => Thrift::Types::MAP, :name => 'i64_map', :key => {:type => Thrift::Types::I64}, :value => {:type => Thrift::Types::STRUCT, :class => Nested3}},
+ 4 => {:type => Thrift::Types::MAP, :name => 'dbl_map', :key => {:type => Thrift::Types::DOUBLE}, :value => {:type => Thrift::Types::STRUCT, :class => Nested3}},
+ 5 => {:type => Thrift::Types::MAP, :name => 'str_map', :key => {:type => Thrift::Types::STRING}, :value => {:type => Thrift::Types::STRUCT, :class => Nested3}}
+ }
+ end
+ end
+end
diff --git a/test/rb/generation/test_struct.rb b/test/rb/generation/test_struct.rb
index 3af0df8..b4526ab 100644
--- a/test/rb/generation/test_struct.rb
+++ b/test/rb/generation/test_struct.rb
@@ -17,6 +17,9 @@
assert_kind_of(Hash, hello.complex)
assert_equal(hello.complex, { 6243 => 632, 2355 => 532, 23 => 532})
+
+ bool_passer = TestNamespace::BoolPasser.new(:value => false)
+ assert_equal false, bool_passer.value
end
def test_goodbyez
diff --git a/test/rb/integration/accelerated_buffered_client.rb b/test/rb/integration/accelerated_buffered_client.rb
new file mode 100644
index 0000000..a27787c
--- /dev/null
+++ b/test/rb/integration/accelerated_buffered_client.rb
@@ -0,0 +1,145 @@
+require File.join(File.dirname(__FILE__), '../test_helper')
+
+require 'thrift'
+require 'thrift/protocol/binaryprotocolaccelerated'
+require 'ThriftTest'
+
+class AcceleratedBufferedClientTest < Test::Unit::TestCase
+ def setup
+ unless @socket
+ @socket = Thrift::Socket.new('localhost', 9090)
+ @protocol = Thrift::BinaryProtocolAccelerated.new(Thrift::BufferedTransport.new(@socket))
+ @client = Thrift::Test::ThriftTest::Client.new(@protocol)
+ @socket.open
+ end
+ end
+
+ def test_string
+ assert_equal(@client.testString('string'), 'string')
+ end
+
+ def test_byte
+ val = 8
+ assert_equal(@client.testByte(val), val)
+ assert_equal(@client.testByte(-val), -val)
+ end
+
+ def test_i32
+ val = 32
+ assert_equal(@client.testI32(val), val)
+ assert_equal(@client.testI32(-val), -val)
+ end
+
+ def test_i64
+ val = 64
+ assert_equal(@client.testI64(val), val)
+ assert_equal(@client.testI64(-val), -val)
+ end
+
+ def test_double
+ val = 3.14
+ assert_equal(@client.testDouble(val), val)
+ assert_equal(@client.testDouble(-val), -val)
+ assert_kind_of(Float, @client.testDouble(val))
+ end
+
+ def test_map
+ val = {1 => 1, 2 => 2, 3 => 3}
+ assert_equal(@client.testMap(val), val)
+ assert_kind_of(Hash, @client.testMap(val))
+ end
+
+ def test_list
+ val = [1,2,3,4,5]
+ assert_equal(@client.testList(val), val)
+ assert_kind_of(Array, @client.testList(val))
+ end
+
+ def test_enum
+ val = Thrift::Test::Numberz::SIX
+ ret = @client.testEnum(val)
+
+ assert_equal(ret, 6)
+ assert_kind_of(Fixnum, ret)
+ end
+
+ def test_typedef
+ #UserId testTypedef(1: UserId thing),
+ true
+ end
+
+ def test_set
+ val = Set.new([1,2,3])
+ assert_equal(@client.testSet(val), val)
+ assert_kind_of(Set, @client.testSet(val))
+ end
+
+ def get_struct
+ Thrift::Test::Xtruct.new({'string_thing' => 'hi!', 'i32_thing' => 4 })
+ end
+
+ def test_struct
+ ret = @client.testStruct(get_struct)
+
+ assert_nil(ret.byte_thing, nil)
+ assert_nil(ret.i64_thing, nil)
+ assert_equal(ret.string_thing, 'hi!')
+ assert_equal(ret.i32_thing, 4)
+ assert_kind_of(Thrift::Test::Xtruct, ret)
+ end
+
+ def test_nest
+ struct2 = Thrift::Test::Xtruct2.new({'struct_thing' => get_struct, 'i32_thing' => 10})
+
+ ret = @client.testNest(struct2)
+
+ assert_nil(ret.struct_thing.byte_thing, nil)
+ assert_nil(ret.struct_thing.i64_thing, nil)
+ assert_equal(ret.struct_thing.string_thing, 'hi!')
+ assert_equal(ret.struct_thing.i32_thing, 4)
+ assert_equal(ret.i32_thing, 10)
+
+ assert_kind_of(Thrift::Test::Xtruct, ret.struct_thing)
+ assert_kind_of(Thrift::Test::Xtruct2, ret)
+ end
+
+ def test_insane
+ insane = Thrift::Test::Insanity.new({
+ 'userMap' => { Thrift::Test::Numberz::ONE => 44 },
+ 'xtructs' => [get_struct,
+ Thrift::Test::Xtruct.new({
+ 'string_thing' => 'hi again',
+ 'i32_thing' => 12
+ })
+ ]
+ })
+
+ ret = @client.testInsanity(insane)
+
+ assert_not_nil(ret[44])
+ assert_not_nil(ret[44][1])
+
+ struct = ret[44][1]
+
+ assert_equal(struct.userMap[Thrift::Test::Numberz::ONE], 44)
+ assert_equal(struct.xtructs[1].string_thing, 'hi again')
+ assert_equal(struct.xtructs[1].i32_thing, 12)
+
+ assert_kind_of(Hash, struct.userMap)
+ assert_kind_of(Array, struct.xtructs)
+ assert_kind_of(Thrift::Test::Insanity, struct)
+ end
+
+ def test_map_map
+ ret = @client.testMapMap(4)
+ assert_kind_of(Hash, ret)
+ assert_equal(ret, { 4 => { 4 => 4}})
+ end
+
+ def test_exception
+ assert_raise Thrift::Test::Xception do
+ @client.testException('foo')
+ end
+ end
+end
+
diff --git a/test/rb/integration/accelerated_buffered_server.rb b/test/rb/integration/accelerated_buffered_server.rb
new file mode 100644
index 0000000..d505e9f
--- /dev/null
+++ b/test/rb/integration/accelerated_buffered_server.rb
@@ -0,0 +1,47 @@
+$:.push File.dirname(__FILE__) + '/../gen-rb'
+$:.push File.join(File.dirname(__FILE__), '../../../lib/rb/lib')
+$:.push File.join(File.dirname(__FILE__), '../../../lib/rb/ext')
+
+require 'thrift'
+require 'thrift/protocol/binaryprotocolaccelerated'
+require 'ThriftTest'
+
+class SimpleHandler
+ [:testString, :testByte, :testI32, :testI64, :testDouble,
+ :testStruct, :testMap, :testSet, :testList, :testNest,
+ :testEnum, :testTypedef].each do |meth|
+
+ define_method(meth) do |thing|
+ thing
+ end
+
+ end
+
+ def testInsanity(thing)
+ num, uid = thing.userMap.find { true }
+ return {uid => {num => thing}}
+ end
+
+ def testMapMap(thing)
+ return {thing => {thing => thing}}
+ end
+
+ def testEnum(thing)
+ return thing
+ end
+
+ def testTypedef(thing)
+ return thing
+ end
+
+ def testException(thing)
+ raise Thrift::Test::Xception, :message => 'error'
+ end
+end
+
+@handler = SimpleHandler.new
+@processor = Thrift::Test::ThriftTest::Processor.new(@handler)
+@transport = Thrift::ServerSocket.new(9090)
+@server = Thrift::ThreadedServer.new(@processor, @transport, Thrift::BufferedTransportFactory.new, Thrift::BinaryProtocolAcceleratedFactory.new)
+
+@server.serve
diff --git a/test/rb/integration/buffered_client.rb b/test/rb/integration/buffered_client.rb
new file mode 100644
index 0000000..3548b18
--- /dev/null
+++ b/test/rb/integration/buffered_client.rb
@@ -0,0 +1,145 @@
+require File.join(File.dirname(__FILE__), '../test_helper')
+
+require 'thrift'
+require 'thrift/protocol/binaryprotocol'
+require 'ThriftTest'
+
+class BufferedClientTest < Test::Unit::TestCase
+ def setup
+ unless @socket
+ @socket = Thrift::Socket.new('localhost', 9090)
+ @protocol = Thrift::BinaryProtocol.new(Thrift::BufferedTransport.new(@socket))
+ @client = Thrift::Test::ThriftTest::Client.new(@protocol)
+ @socket.open
+ end
+ end
+
+ def test_string
+ assert_equal(@client.testString('string'), 'string')
+ end
+
+ def test_byte
+ val = 8
+ assert_equal(@client.testByte(val), val)
+ assert_equal(@client.testByte(-val), -val)
+ end
+
+ def test_i32
+ val = 32
+ assert_equal(@client.testI32(val), val)
+ assert_equal(@client.testI32(-val), -val)
+ end
+
+ def test_i64
+ val = 64
+ assert_equal(@client.testI64(val), val)
+ assert_equal(@client.testI64(-val), -val)
+ end
+
+ def test_double
+ val = 3.14
+ assert_equal(@client.testDouble(val), val)
+ assert_equal(@client.testDouble(-val), -val)
+ assert_kind_of(Float, @client.testDouble(val))
+ end
+
+ def test_map
+ val = {1 => 1, 2 => 2, 3 => 3}
+ assert_equal(@client.testMap(val), val)
+ assert_kind_of(Hash, @client.testMap(val))
+ end
+
+ def test_list
+ val = [1,2,3,4,5]
+ assert_equal(@client.testList(val), val)
+ assert_kind_of(Array, @client.testList(val))
+ end
+
+ def test_enum
+ val = Thrift::Test::Numberz::SIX
+ ret = @client.testEnum(val)
+
+ assert_equal(ret, 6)
+ assert_kind_of(Fixnum, ret)
+ end
+
+ def test_typedef
+ #UserId testTypedef(1: UserId thing),
+ true
+ end
+
+ def test_set
+ val = Set.new([1,2,3])
+ assert_equal(@client.testSet(val), val)
+ assert_kind_of(Set, @client.testSet(val))
+ end
+
+ def get_struct
+ Thrift::Test::Xtruct.new({'string_thing' => 'hi!', 'i32_thing' => 4 })
+ end
+
+ def test_struct
+ ret = @client.testStruct(get_struct)
+
+ assert_nil(ret.byte_thing, nil)
+ assert_nil(ret.i64_thing, nil)
+ assert_equal(ret.string_thing, 'hi!')
+ assert_equal(ret.i32_thing, 4)
+ assert_kind_of(Thrift::Test::Xtruct, ret)
+ end
+
+ def test_nest
+ struct2 = Thrift::Test::Xtruct2.new({'struct_thing' => get_struct, 'i32_thing' => 10})
+
+ ret = @client.testNest(struct2)
+
+ assert_nil(ret.struct_thing.byte_thing, nil)
+ assert_nil(ret.struct_thing.i64_thing, nil)
+ assert_equal(ret.struct_thing.string_thing, 'hi!')
+ assert_equal(ret.struct_thing.i32_thing, 4)
+ assert_equal(ret.i32_thing, 10)
+
+ assert_kind_of(Thrift::Test::Xtruct, ret.struct_thing)
+ assert_kind_of(Thrift::Test::Xtruct2, ret)
+ end
+
+ def test_insane
+ insane = Thrift::Test::Insanity.new({
+ 'userMap' => { Thrift::Test::Numberz::ONE => 44 },
+ 'xtructs' => [get_struct,
+ Thrift::Test::Xtruct.new({
+ 'string_thing' => 'hi again',
+ 'i32_thing' => 12
+ })
+ ]
+ })
+
+ ret = @client.testInsanity(insane)
+
+ assert_not_nil(ret[44])
+ assert_not_nil(ret[44][1])
+
+ struct = ret[44][1]
+
+ assert_equal(struct.userMap[Thrift::Test::Numberz::ONE], 44)
+ assert_equal(struct.xtructs[1].string_thing, 'hi again')
+ assert_equal(struct.xtructs[1].i32_thing, 12)
+
+ assert_kind_of(Hash, struct.userMap)
+ assert_kind_of(Array, struct.xtructs)
+ assert_kind_of(Thrift::Test::Insanity, struct)
+ end
+
+ def test_map_map
+ ret = @client.testMapMap(4)
+ assert_kind_of(Hash, ret)
+ assert_equal(ret, { 4 => { 4 => 4}})
+ end
+
+ def test_exception
+ assert_raise Thrift::Test::Xception do
+ @client.testException('foo')
+ end
+ end
+end
+
diff --git a/test/rb/integration/simple_client.rb b/test/rb/integration/simple_client.rb
new file mode 100644
index 0000000..fe2ed82
--- /dev/null
+++ b/test/rb/integration/simple_client.rb
@@ -0,0 +1,145 @@
+require File.join(File.dirname(__FILE__), '../test_helper')
+
+require 'thrift'
+require 'thrift/protocol/binaryprotocol'
+require 'ThriftTest'
+
+class SimpleClientTest < Test::Unit::TestCase
+ def setup
+ unless @socket
+ @socket = Thrift::Socket.new('localhost', 9090)
+ @protocol = Thrift::BinaryProtocol.new(@socket)
+ @client = Thrift::Test::ThriftTest::Client.new(@protocol)
+ @socket.open
+ end
+ end
+
+ def test_string
+ assert_equal(@client.testString('string'), 'string')
+ end
+
+ def test_byte
+ val = 8
+ assert_equal(@client.testByte(val), val)
+ assert_equal(@client.testByte(-val), -val)
+ end
+
+ def test_i32
+ val = 32
+ assert_equal(@client.testI32(val), val)
+ assert_equal(@client.testI32(-val), -val)
+ end
+
+ def test_i64
+ val = 64
+ assert_equal(@client.testI64(val), val)
+ assert_equal(@client.testI64(-val), -val)
+ end
+
+ def test_double
+ val = 3.14
+ assert_equal(@client.testDouble(val), val)
+ assert_equal(@client.testDouble(-val), -val)
+ assert_kind_of(Float, @client.testDouble(val))
+ end
+
+ def test_map
+ val = {1 => 1, 2 => 2, 3 => 3}
+ assert_equal(@client.testMap(val), val)
+ assert_kind_of(Hash, @client.testMap(val))
+ end
+
+ def test_list
+ val = [1,2,3,4,5]
+ assert_equal(@client.testList(val), val)
+ assert_kind_of(Array, @client.testList(val))
+ end
+
+ def test_enum
+ val = Thrift::Test::Numberz::SIX
+ ret = @client.testEnum(val)
+
+ assert_equal(ret, 6)
+ assert_kind_of(Fixnum, ret)
+ end
+
+ def test_typedef
+ #UserId testTypedef(1: UserId thing),
+ true
+ end
+
+ def test_set
+ val = Set.new([1,2,3])
+ assert_equal(@client.testSet(val), val)
+ assert_kind_of(Set, @client.testSet(val))
+ end
+
+ def get_struct
+ Thrift::Test::Xtruct.new({'string_thing' => 'hi!', 'i32_thing' => 4 })
+ end
+
+ def test_struct
+ ret = @client.testStruct(get_struct)
+
+ assert_nil(ret.byte_thing, nil)
+ assert_nil(ret.i64_thing, nil)
+ assert_equal(ret.string_thing, 'hi!')
+ assert_equal(ret.i32_thing, 4)
+ assert_kind_of(Thrift::Test::Xtruct, ret)
+ end
+
+ def test_nest
+ struct2 = Thrift::Test::Xtruct2.new({'struct_thing' => get_struct, 'i32_thing' => 10})
+
+ ret = @client.testNest(struct2)
+
+ assert_nil(ret.struct_thing.byte_thing, nil)
+ assert_nil(ret.struct_thing.i64_thing, nil)
+ assert_equal(ret.struct_thing.string_thing, 'hi!')
+ assert_equal(ret.struct_thing.i32_thing, 4)
+ assert_equal(ret.i32_thing, 10)
+
+ assert_kind_of(Thrift::Test::Xtruct, ret.struct_thing)
+ assert_kind_of(Thrift::Test::Xtruct2, ret)
+ end
+
+ def test_insane
+ insane = Thrift::Test::Insanity.new({
+ 'userMap' => { Thrift::Test::Numberz::ONE => 44 },
+ 'xtructs' => [get_struct,
+ Thrift::Test::Xtruct.new({
+ 'string_thing' => 'hi again',
+ 'i32_thing' => 12
+ })
+ ]
+ })
+
+ ret = @client.testInsanity(insane)
+
+ assert_not_nil(ret[44])
+ assert_not_nil(ret[44][1])
+
+ struct = ret[44][1]
+
+ assert_equal(struct.userMap[Thrift::Test::Numberz::ONE], 44)
+ assert_equal(struct.xtructs[1].string_thing, 'hi again')
+ assert_equal(struct.xtructs[1].i32_thing, 12)
+
+ assert_kind_of(Hash, struct.userMap)
+ assert_kind_of(Array, struct.xtructs)
+ assert_kind_of(Thrift::Test::Insanity, struct)
+ end
+
+ def test_map_map
+ ret = @client.testMapMap(4)
+ assert_kind_of(Hash, ret)
+ assert_equal(ret, { 4 => { 4 => 4}})
+ end
+
+ def test_exception
+ assert_raise Thrift::Test::Xception do
+ @client.testException('foo')
+ end
+ end
+end
+
diff --git a/test/rb/integration/simple_server.rb b/test/rb/integration/simple_server.rb
new file mode 100644
index 0000000..6b029ba
--- /dev/null
+++ b/test/rb/integration/simple_server.rb
@@ -0,0 +1,46 @@
+$:.push File.dirname(__FILE__) + '/../gen-rb'
+$:.push File.join(File.dirname(__FILE__), '../../../lib/rb/lib')
+
+require 'thrift'
+require 'thrift/protocol/binaryprotocol'
+require 'ThriftTest'
+
+class SimpleHandler
+ [:testString, :testByte, :testI32, :testI64, :testDouble,
+ :testStruct, :testMap, :testSet, :testList, :testNest,
+ :testEnum, :testTypedef].each do |meth|
+
+ define_method(meth) do |thing|
+ thing
+ end
+
+ end
+
+ def testInsanity(thing)
+ num, uid = thing.userMap.find { true }
+ return {uid => {num => thing}}
+ end
+
+ def testMapMap(thing)
+ return {thing => {thing => thing}}
+ end
+
+ def testEnum(thing)
+ return thing
+ end
+
+ def testTypedef(thing)
+ return thing
+ end
+
+ def testException(thing)
+ raise Thrift::Test::Xception, :message => 'error'
+ end
+end
+
+@handler = SimpleHandler.new
+@processor = Thrift::Test::ThriftTest::Processor.new(@handler)
+@transport = Thrift::ServerSocket.new(9090)
+@server = Thrift::ThreadedServer.new(@processor, @transport)
+
+@server.serve
diff --git a/test/rb/test_helper.rb b/test/rb/test_helper.rb
index 125f52f..51f72b1 100644
--- a/test/rb/test_helper.rb
+++ b/test/rb/test_helper.rb
@@ -1,4 +1,16 @@
$:.unshift File.dirname(__FILE__) + '/gen-rb'
$:.unshift File.join(File.dirname(__FILE__), '../../lib/rb/lib')
+$:.unshift File.join(File.dirname(__FILE__), '../../lib/rb/ext')
require 'test/unit'
+
+module Thrift
+ module Struct
+ def ==(other)
+ return false unless other.is_a? self.class
+ self.class.const_get(:FIELDS).collect {|fid, data| data[:name] }.all? do |field|
+ send(field) == other.send(field)
+ end
+ end
+ end
+end
diff --git a/test/rb/test_suite.rb b/test/rb/test_suite.rb
index af686a8..3611963 100644
--- a/test/rb/test_suite.rb
+++ b/test/rb/test_suite.rb
@@ -1 +1 @@
-Dir["{core,generation,integration}/**/*.rb"].each {|f| require f }
\ No newline at end of file
+Dir["{core,generation}/**/*.rb"].each {|f| require f }
\ No newline at end of file