Merge pull request #340 from skippy/add-array-like-methods

ruby: make repeated_field quack like an array
pull/252/merge
Chris Fallin 10 years ago
commit b481a4f920
  1. 4
      ruby/ext/google/protobuf_c/protobuf.h
  2. 125
      ruby/ext/google/protobuf_c/repeated_field.c
  3. 152
      ruby/lib/google/protobuf/repeated_field.rb
  4. 43
      ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java
  5. 136
      ruby/src/main/java/com/google/protobuf/jruby/RubyRepeatedField.java
  6. 4
      ruby/tests/basic.rb
  7. 640
      ruby/tests/repeated_field_test.rb

@ -361,13 +361,13 @@ extern VALUE cRepeatedField;
RepeatedField* ruby_to_RepeatedField(VALUE value); RepeatedField* ruby_to_RepeatedField(VALUE value);
VALUE RepeatedField_each(VALUE _self); VALUE RepeatedField_each(VALUE _self);
VALUE RepeatedField_index(VALUE _self, VALUE _index); VALUE RepeatedField_index(int argc, VALUE* argv, VALUE _self);
void* RepeatedField_index_native(VALUE _self, int index); void* RepeatedField_index_native(VALUE _self, int index);
VALUE RepeatedField_index_set(VALUE _self, VALUE _index, VALUE val); VALUE RepeatedField_index_set(VALUE _self, VALUE _index, VALUE val);
void RepeatedField_reserve(RepeatedField* self, int new_size); void RepeatedField_reserve(RepeatedField* self, int new_size);
VALUE RepeatedField_push(VALUE _self, VALUE val); VALUE RepeatedField_push(VALUE _self, VALUE val);
void RepeatedField_push_native(VALUE _self, void* data); void RepeatedField_push_native(VALUE _self, void* data);
VALUE RepeatedField_pop(VALUE _self); VALUE RepeatedField_pop_one(VALUE _self);
VALUE RepeatedField_insert(int argc, VALUE* argv, VALUE _self); VALUE RepeatedField_insert(int argc, VALUE* argv, VALUE _self);
VALUE RepeatedField_replace(VALUE _self, VALUE list); VALUE RepeatedField_replace(VALUE _self, VALUE list);
VALUE RepeatedField_clear(VALUE _self); VALUE RepeatedField_clear(VALUE _self);

@ -55,6 +55,21 @@ static int index_position(VALUE _index, RepeatedField* repeated_field) {
return index; return index;
} }
VALUE RepeatedField_subarray(VALUE _self, long beg, long len) {
RepeatedField* self = ruby_to_RepeatedField(_self);
int element_size = native_slot_size(self->field_type);
upb_fieldtype_t field_type = self->field_type;
VALUE field_type_class = self->field_type_class;
size_t off = beg * element_size;
VALUE ary = rb_ary_new2(len);
for (int i = beg; i < beg + len; i++, off += element_size) {
void* mem = ((uint8_t *)self->elements) + off;
VALUE elem = native_slot_get(field_type, field_type_class, mem);
rb_ary_push(ary, elem);
}
return ary;
}
/* /*
* call-seq: * call-seq:
@ -76,28 +91,57 @@ VALUE RepeatedField_each(VALUE _self) {
VALUE val = native_slot_get(field_type, field_type_class, memory); VALUE val = native_slot_get(field_type, field_type_class, memory);
rb_yield(val); rb_yield(val);
} }
return Qnil; return _self;
} }
/* /*
* call-seq: * call-seq:
* RepeatedField.[](index) => value * RepeatedField.[](index) => value
* *
* Accesses the element at the given index. Returns nil on out-of-bounds * Accesses the element at the given index. Returns nil on out-of-bounds
*/ */
VALUE RepeatedField_index(VALUE _self, VALUE _index) { VALUE RepeatedField_index(int argc, VALUE* argv, VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self); RepeatedField* self = ruby_to_RepeatedField(_self);
int element_size = native_slot_size(self->field_type); int element_size = native_slot_size(self->field_type);
upb_fieldtype_t field_type = self->field_type; upb_fieldtype_t field_type = self->field_type;
VALUE field_type_class = self->field_type_class; VALUE field_type_class = self->field_type_class;
int index = index_position(_index, self); VALUE arg = argv[0];
if (index < 0 || index >= self->size) { long beg, len;
if (argc == 1){
if (FIXNUM_P(arg)) {
/* standard case */
int index = index_position(argv[0], self);
if (index < 0 || index >= self->size) {
return Qnil;
}
void* memory = (void *) (((uint8_t *)self->elements) + index * element_size);
return native_slot_get(field_type, field_type_class, memory);
}else{
/* check if idx is Range */
size_t off;
switch (rb_range_beg_len(arg, &beg, &len, self->size, 0)) {
case Qfalse:
break;
case Qnil:
return Qnil;
default:
return RepeatedField_subarray(_self, beg, len);
}
}
}
/* assume 2 arguments */
beg = NUM2LONG(argv[0]);
len = NUM2LONG(argv[1]);
if (beg < 0) {
beg += self->size;
}
if (beg >= self->size) {
return Qnil; return Qnil;
} }
return RepeatedField_subarray(_self, beg, len);
void* memory = (void *) (((uint8_t *)self->elements) + index * element_size);
return native_slot_get(field_type, field_type_class, memory);
} }
/* /*
@ -173,6 +217,7 @@ VALUE RepeatedField_push(VALUE _self, VALUE val) {
return _self; return _self;
} }
// Used by parsing handlers. // Used by parsing handlers.
void RepeatedField_push_native(VALUE _self, void* data) { void RepeatedField_push_native(VALUE _self, void* data) {
RepeatedField* self = ruby_to_RepeatedField(_self); RepeatedField* self = ruby_to_RepeatedField(_self);
@ -193,19 +238,15 @@ void* RepeatedField_index_native(VALUE _self, int index) {
} }
/* /*
* call-seq: * Private ruby method, used by RepeatedField.pop
* RepeatedField.pop => value
*
* Removes the last element and returns it. Throws an exception if the repeated
* field is empty.
*/ */
VALUE RepeatedField_pop(VALUE _self) { VALUE RepeatedField_pop_one(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self); RepeatedField* self = ruby_to_RepeatedField(_self);
upb_fieldtype_t field_type = self->field_type; upb_fieldtype_t field_type = self->field_type;
VALUE field_type_class = self->field_type_class; VALUE field_type_class = self->field_type_class;
int element_size = native_slot_size(field_type); int element_size = native_slot_size(field_type);
if (self->size == 0) { if (self->size == 0) {
rb_raise(rb_eRangeError, "Pop from empty repeated field is not allowed."); return Qnil;
} }
int index = self->size - 1; int index = self->size - 1;
void* memory = (void *) (((uint8_t *)self->elements) + index * element_size); void* memory = (void *) (((uint8_t *)self->elements) + index * element_size);
@ -214,19 +255,6 @@ VALUE RepeatedField_pop(VALUE _self) {
return ret; return ret;
} }
/*
* call-seq:
* RepeatedField.insert(*args)
*
* Pushes each arg in turn onto the end of the repeated field.
*/
VALUE RepeatedField_insert(int argc, VALUE* argv, VALUE _self) {
for (int i = 0; i < argc; i++) {
RepeatedField_push(_self, argv[i]);
}
return Qnil;
}
/* /*
* call-seq: * call-seq:
* RepeatedField.replace(list) * RepeatedField.replace(list)
@ -240,7 +268,7 @@ VALUE RepeatedField_replace(VALUE _self, VALUE list) {
for (int i = 0; i < RARRAY_LEN(list); i++) { for (int i = 0; i < RARRAY_LEN(list); i++) {
RepeatedField_push(_self, rb_ary_entry(list, i)); RepeatedField_push(_self, rb_ary_entry(list, i));
} }
return Qnil; return list;
} }
/* /*
@ -252,7 +280,7 @@ VALUE RepeatedField_replace(VALUE _self, VALUE list) {
VALUE RepeatedField_clear(VALUE _self) { VALUE RepeatedField_clear(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self); RepeatedField* self = ruby_to_RepeatedField(_self);
self->size = 0; self->size = 0;
return Qnil; return _self;
} }
/* /*
@ -341,7 +369,6 @@ VALUE RepeatedField_to_ary(VALUE _self) {
for (int i = 0; i < self->size; i++, off += elem_size) { for (int i = 0; i < self->size; i++, off += elem_size) {
void* mem = ((uint8_t *)self->elements) + off; void* mem = ((uint8_t *)self->elements) + off;
VALUE elem = native_slot_get(field_type, self->field_type_class, mem); VALUE elem = native_slot_get(field_type, self->field_type_class, mem);
rb_ary_push(ary, elem); rb_ary_push(ary, elem);
} }
return ary; return ary;
@ -417,19 +444,6 @@ VALUE RepeatedField_hash(VALUE _self) {
return hash; return hash;
} }
/*
* call-seq:
* RepeatedField.inspect => string
*
* Returns a string representing this repeated field's elements. It will be
* formated as "[<element>, <element>, ...]", with each element's string
* representation computed by its own #inspect method.
*/
VALUE RepeatedField_inspect(VALUE _self) {
VALUE self_ary = RepeatedField_to_ary(_self);
return rb_funcall(self_ary, rb_intern("inspect"), 0);
}
/* /*
* call-seq: * call-seq:
* RepeatedField.+(other) => repeated field * RepeatedField.+(other) => repeated field
@ -466,6 +480,22 @@ VALUE RepeatedField_plus(VALUE _self, VALUE list) {
return dupped; return dupped;
} }
/*
* call-seq:
* RepeatedField.concat(other) => self
*
* concats the passed in array to self. Returns a Ruby array.
*/
VALUE RepeatedField_concat(VALUE _self, VALUE list) {
RepeatedField* self = ruby_to_RepeatedField(_self);
Check_Type(list, T_ARRAY);
for (int i = 0; i < RARRAY_LEN(list); i++) {
RepeatedField_push(_self, rb_ary_entry(list, i));
}
return _self;
}
void validate_type_class(upb_fieldtype_t type, VALUE klass) { void validate_type_class(upb_fieldtype_t type, VALUE klass) {
if (rb_iv_get(klass, kDescriptorInstanceVar) == Qnil) { if (rb_iv_get(klass, kDescriptorInstanceVar) == Qnil) {
rb_raise(rb_eArgError, rb_raise(rb_eArgError,
@ -585,22 +615,23 @@ void RepeatedField_register(VALUE module) {
rb_define_method(klass, "initialize", rb_define_method(klass, "initialize",
RepeatedField_init, -1); RepeatedField_init, -1);
rb_define_method(klass, "each", RepeatedField_each, 0); rb_define_method(klass, "each", RepeatedField_each, 0);
rb_define_method(klass, "[]", RepeatedField_index, 1); rb_define_method(klass, "[]", RepeatedField_index, -1);
rb_define_method(klass, "at", RepeatedField_index, -1);
rb_define_method(klass, "[]=", RepeatedField_index_set, 2); rb_define_method(klass, "[]=", RepeatedField_index_set, 2);
rb_define_method(klass, "push", RepeatedField_push, 1); rb_define_method(klass, "push", RepeatedField_push, 1);
rb_define_method(klass, "<<", RepeatedField_push, 1); rb_define_method(klass, "<<", RepeatedField_push, 1);
rb_define_method(klass, "pop", RepeatedField_pop, 0); rb_define_private_method(klass, "pop_one", RepeatedField_pop_one, 0);
rb_define_method(klass, "insert", RepeatedField_insert, -1);
rb_define_method(klass, "replace", RepeatedField_replace, 1); rb_define_method(klass, "replace", RepeatedField_replace, 1);
rb_define_method(klass, "clear", RepeatedField_clear, 0); rb_define_method(klass, "clear", RepeatedField_clear, 0);
rb_define_method(klass, "length", RepeatedField_length, 0); rb_define_method(klass, "length", RepeatedField_length, 0);
rb_define_method(klass, "size", RepeatedField_length, 0);
rb_define_method(klass, "dup", RepeatedField_dup, 0); rb_define_method(klass, "dup", RepeatedField_dup, 0);
// Also define #clone so that we don't inherit Object#clone. // Also define #clone so that we don't inherit Object#clone.
rb_define_method(klass, "clone", RepeatedField_dup, 0); rb_define_method(klass, "clone", RepeatedField_dup, 0);
rb_define_method(klass, "==", RepeatedField_eq, 1); rb_define_method(klass, "==", RepeatedField_eq, 1);
rb_define_method(klass, "to_ary", RepeatedField_to_ary, 0); rb_define_method(klass, "to_ary", RepeatedField_to_ary, 0);
rb_define_method(klass, "hash", RepeatedField_hash, 0); rb_define_method(klass, "hash", RepeatedField_hash, 0);
rb_define_method(klass, "inspect", RepeatedField_inspect, 0);
rb_define_method(klass, "+", RepeatedField_plus, 1); rb_define_method(klass, "+", RepeatedField_plus, 1);
rb_define_method(klass, "concat", RepeatedField_concat, 1);
rb_include_module(klass, rb_mEnumerable); rb_include_module(klass, rb_mEnumerable);
} }

@ -28,12 +28,160 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# add syntatic sugar on top of the core library require 'forwardable'
#
# This class makes RepeatedField act (almost-) like a Ruby Array.
# It has convenience methods that extend the core C or Java based
# methods.
#
# This is a best-effort to mirror Array behavior. Two comments:
# 1) patches always welcome :)
# 2) if performance is an issue, feel free to rewrite the method
# in jruby and C. The source code has plenty of examples
#
# KNOWN ISSUES
# - #[]= doesn't allow less used approaches such as `arr[1, 2] = 'fizz'`
# - #concat should return the orig array
# - #push should accept multiple arguments and push them all at the same time
#
module Google module Google
module Protobuf module Protobuf
class RepeatedField class RepeatedField
extend Forwardable
# methods defined in C or Java:
# +
# [], at
# []=
# concat
# clear
# dup, clone
# each
# push, <<
# replace
# length, size
# ==
# to_ary, to_a
# also all enumerable
#
# NOTE: using delegators rather than method_missing to make the
# relationship explicit instead of implicit
def_delegators :to_ary,
:&, :*, :-, :'<=>',
:assoc, :bsearch, :combination, :compact, :count, :cycle,
:drop, :drop_while, :eql?, :fetch, :find_index, :flatten,
:include?, :index, :inspect, :join,
:pack, :permutation, :product, :pretty_print, :pretty_print_cycle,
:rassoc, :repeated_combination, :repeated_permutation, :reverse,
:rindex, :rotate, :sample, :shuffle, :shelljoin, :slice,
:to_s, :transpose, :uniq, :|
def first(n=nil)
n ? self[0..n] : self[0]
end
def last(n=nil)
n ? self[(self.size-n-1)..-1] : self[-1]
end
def pop(n=nil)
if n
results = []
n.times{ results << pop_one }
return results
else
return pop_one
end
end
def empty?
self.size == 0
end
# array aliases into enumerable
alias_method :each_index, :each_with_index
alias_method :slice, :[]
alias_method :values_at, :select
alias_method :map, :collect
class << self
def define_array_wrapper_method(method_name)
define_method(method_name) do |*args, &block|
arr = self.to_a
result = arr.send(method_name, *args)
self.replace(arr)
return result if result
return block ? block.call : result
end
end
private :define_array_wrapper_method
def define_array_wrapper_with_result_method(method_name)
define_method(method_name) do |*args, &block|
# result can be an Enumerator, Array, or nil
# Enumerator can sometimes be returned if a block is an optional argument and it is not passed in
# nil usually specifies that no change was made
result = self.to_a.send(method_name, *args, &block)
if result
new_arr = result.to_a
self.replace(new_arr)
if result.is_a?(Enumerator)
# generate a fresh enum; rewinding the exiting one, in Ruby 2.2, will
# reset the enum with the same length, but all the #next calls will
# return nil
result = new_arr.to_enum
# generate a wrapper enum so any changes which occur by a chained
# enum can be captured
ie = ProxyingEnumerator.new(self, result)
result = ie.to_enum
end
end
result
end
end
private :define_array_wrapper_with_result_method
end
%w(delete delete_at delete_if shift slice! unshift).each do |method_name|
define_array_wrapper_method(method_name)
end
%w(collect! compact! fill flatten! insert reverse!
rotate! select! shuffle! sort! sort_by! uniq!).each do |method_name|
define_array_wrapper_with_result_method(method_name)
end
alias_method :keep_if, :select!
alias_method :map!, :collect!
alias_method :reject!, :delete_if
# propagates changes made by user of enumerator back to the original repeated field.
# This only applies in cases where the calling function which created the enumerator,
# such as #sort!, modifies itself rather than a new array, such as #sort
class ProxyingEnumerator < Struct.new(:repeated_field, :external_enumerator)
def each(*args, &block)
results = []
external_enumerator.each_with_index do |val, i|
result = yield(val)
results << result
#nil means no change occured from yield; usually occurs when #to_a is called
if result
repeated_field[i] = result if result != val
end
end
results
end
end
alias_method :size, :length
end end
end end

@ -71,6 +71,17 @@ public class RubyFieldDescriptor extends RubyObject {
return this; return this;
} }
/*
* call-seq:
* FieldDescriptor.label
*
* Return the label of this field.
*/
@JRubyMethod(name = "label")
public IRubyObject getLabel(ThreadContext context) {
return this.label;
}
/* /*
* call-seq: * call-seq:
* FieldDescriptor.label = label * FieldDescriptor.label = label
@ -80,8 +91,10 @@ public class RubyFieldDescriptor extends RubyObject {
*/ */
@JRubyMethod(name = "label=") @JRubyMethod(name = "label=")
public IRubyObject setLabel(ThreadContext context, IRubyObject value) { public IRubyObject setLabel(ThreadContext context, IRubyObject value) {
String labelName = value.asJavaString();
this.label = context.runtime.newSymbol(labelName.toLowerCase());
this.builder.setLabel( this.builder.setLabel(
DescriptorProtos.FieldDescriptorProto.Label.valueOf("LABEL_" + value.asJavaString().toUpperCase())); DescriptorProtos.FieldDescriptorProto.Label.valueOf("LABEL_" + labelName.toUpperCase()));
return context.runtime.getNil(); return context.runtime.getNil();
} }
@ -89,18 +102,13 @@ public class RubyFieldDescriptor extends RubyObject {
* call-seq: * call-seq:
* FieldDescriptor.name => name * FieldDescriptor.name => name
* *
* Returns the name of this field. * Returns the name of this field as a Ruby String, or nil if it is not set.
*/ */
@JRubyMethod(name = "name") @JRubyMethod(name = "name")
public IRubyObject getName(ThreadContext context) { public IRubyObject getName(ThreadContext context) {
return this.name; return this.name;
} }
@JRubyMethod(name = "subtype")
public IRubyObject getSubType(ThreadContext context) {
return subType;
}
/* /*
* call-seq: * call-seq:
* FieldDescriptor.name = name * FieldDescriptor.name = name
@ -116,6 +124,12 @@ public class RubyFieldDescriptor extends RubyObject {
return context.runtime.getNil(); return context.runtime.getNil();
} }
@JRubyMethod(name = "subtype")
public IRubyObject getSubType(ThreadContext context) {
return subType;
}
/* /*
* call-seq: * call-seq:
* FieldDescriptor.type => type * FieldDescriptor.type => type
@ -144,6 +158,18 @@ public class RubyFieldDescriptor extends RubyObject {
return context.runtime.getNil(); return context.runtime.getNil();
} }
/*
* call-seq:
* FieldDescriptor.number => number
*
* Returns this field's number, as a Ruby Integer, or nil if not yet set.
*
*/
@JRubyMethod(name = "number")
public IRubyObject getnumber(ThreadContext context) {
return this.number;
}
/* /*
* call-seq: * call-seq:
* FieldDescriptor.number = number * FieldDescriptor.number = number
@ -153,6 +179,7 @@ public class RubyFieldDescriptor extends RubyObject {
*/ */
@JRubyMethod(name = "number=") @JRubyMethod(name = "number=")
public IRubyObject setNumber(ThreadContext context, IRubyObject value) { public IRubyObject setNumber(ThreadContext context, IRubyObject value) {
this.number = value;
this.builder.setNumber(RubyNumeric.num2int(value)); this.builder.setNumber(RubyNumeric.num2int(value));
return context.runtime.getNil(); return context.runtime.getNil();
} }
@ -240,6 +267,8 @@ public class RubyFieldDescriptor extends RubyObject {
private DescriptorProtos.FieldDescriptorProto.Builder builder; private DescriptorProtos.FieldDescriptorProto.Builder builder;
private IRubyObject name; private IRubyObject name;
private IRubyObject label;
private IRubyObject number;
private IRubyObject subType; private IRubyObject subType;
private IRubyObject oneofName; private IRubyObject oneofName;
private Descriptors.FieldDescriptor fieldDef; private Descriptors.FieldDescriptor fieldDef;

@ -40,6 +40,7 @@ import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext; import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.builtin.IRubyObject;
import java.util.Arrays;
@JRubyClass(name = "RepeatedClass", include = "Enumerable") @JRubyClass(name = "RepeatedClass", include = "Enumerable")
public class RubyRepeatedField extends RubyObject { public class RubyRepeatedField extends RubyObject {
@ -110,6 +111,10 @@ public class RubyRepeatedField extends RubyObject {
public IRubyObject indexSet(ThreadContext context, IRubyObject index, IRubyObject value) { public IRubyObject indexSet(ThreadContext context, IRubyObject index, IRubyObject value) {
int arrIndex = normalizeArrayIndex(index); int arrIndex = normalizeArrayIndex(index);
Utils.checkType(context, fieldType, value, (RubyModule) typeClass); Utils.checkType(context, fieldType, value, (RubyModule) typeClass);
IRubyObject defaultValue = defaultValue(context);
for (int i = this.storage.size(); i < arrIndex; i++) {
this.storage.set(i, defaultValue);
}
this.storage.set(arrIndex, value); this.storage.set(arrIndex, value);
return context.runtime.getNil(); return context.runtime.getNil();
} }
@ -120,27 +125,35 @@ public class RubyRepeatedField extends RubyObject {
* *
* Accesses the element at the given index. Returns nil on out-of-bounds * Accesses the element at the given index. Returns nil on out-of-bounds
*/ */
@JRubyMethod(name = "[]") @JRubyMethod(required=1, optional=1, name = {"at", "[]"})
public IRubyObject index(ThreadContext context, IRubyObject index) { public IRubyObject index(ThreadContext context, IRubyObject[] args) {
int arrIndex = normalizeArrayIndex(index); if (args.length == 1){
if (arrIndex < 0 || arrIndex >= this.storage.size()) { IRubyObject arg = args[0];
return context.runtime.getNil(); if (Utils.isRubyNum(arg)) {
/* standard case */
int arrIndex = normalizeArrayIndex(arg);
if (arrIndex < 0 || arrIndex >= this.storage.size()) {
return context.runtime.getNil();
}
return this.storage.eltInternal(arrIndex);
} else if (arg instanceof RubyRange) {
RubyRange range = ((RubyRange) arg);
int beg = RubyNumeric.num2int(range.first(context));
int to = RubyNumeric.num2int(range.last(context));
int len = to - beg + 1;
return this.storage.subseq(beg, len);
}
} }
return this.storage.eltInternal(arrIndex); /* assume 2 arguments */
} int beg = RubyNumeric.num2int(args[0]);
int len = RubyNumeric.num2int(args[1]);
/* if (beg < 0) {
* call-seq: beg += this.storage.size();
* RepeatedField.insert(*args)
*
* Pushes each arg in turn onto the end of the repeated field.
*/
@JRubyMethod(rest = true)
public IRubyObject insert(ThreadContext context, IRubyObject[] args) {
for (int i = 0; i < args.length; i++) {
push(context, args[i]);
} }
return context.runtime.getNil(); if (beg >= this.storage.size()) {
return context.runtime.getNil();
}
return this.storage.subseq(beg, len);
} }
/* /*
@ -151,20 +164,19 @@ public class RubyRepeatedField extends RubyObject {
*/ */
@JRubyMethod(name = {"push", "<<"}) @JRubyMethod(name = {"push", "<<"})
public IRubyObject push(ThreadContext context, IRubyObject value) { public IRubyObject push(ThreadContext context, IRubyObject value) {
Utils.checkType(context, fieldType, value, (RubyModule) typeClass); if (!(fieldType == Descriptors.FieldDescriptor.Type.MESSAGE &&
value == context.runtime.getNil())) {
Utils.checkType(context, fieldType, value, (RubyModule) typeClass);
}
this.storage.add(value); this.storage.add(value);
return this; return this.storage;
} }
/* /*
* call-seq: * private Ruby method used by RepeatedField.pop
* RepeatedField.pop => value
*
* Removes the last element and returns it. Throws an exception if the repeated
* field is empty.
*/ */
@JRubyMethod @JRubyMethod(visibility = org.jruby.runtime.Visibility.PRIVATE)
public IRubyObject pop(ThreadContext context) { public IRubyObject pop_one(ThreadContext context) {
IRubyObject ret = this.storage.last(); IRubyObject ret = this.storage.last();
this.storage.remove(ret); this.storage.remove(ret);
return ret; return ret;
@ -181,7 +193,7 @@ public class RubyRepeatedField extends RubyObject {
RubyArray arr = (RubyArray) list; RubyArray arr = (RubyArray) list;
checkArrayElementType(context, arr); checkArrayElementType(context, arr);
this.storage = arr; this.storage = arr;
return context.runtime.getNil(); return this.storage;
} }
/* /*
@ -193,7 +205,7 @@ public class RubyRepeatedField extends RubyObject {
@JRubyMethod @JRubyMethod
public IRubyObject clear(ThreadContext context) { public IRubyObject clear(ThreadContext context) {
this.storage.clear(); this.storage.clear();
return context.runtime.getNil(); return this.storage;
} }
/* /*
@ -202,7 +214,7 @@ public class RubyRepeatedField extends RubyObject {
* *
* Returns the length of this repeated field. * Returns the length of this repeated field.
*/ */
@JRubyMethod(name = {"count", "length"}) @JRubyMethod(name = {"length", "size"})
public IRubyObject length(ThreadContext context) { public IRubyObject length(ThreadContext context) {
return context.runtime.newFixnum(this.storage.size()); return context.runtime.newFixnum(this.storage.size());
} }
@ -215,7 +227,7 @@ public class RubyRepeatedField extends RubyObject {
* repeated field's elements and other's elements. The other (second) list may * repeated field's elements and other's elements. The other (second) list may
* be either another repeated field or a Ruby array. * be either another repeated field or a Ruby array.
*/ */
@JRubyMethod(name = "+") @JRubyMethod(name = {"+"})
public IRubyObject plus(ThreadContext context, IRubyObject list) { public IRubyObject plus(ThreadContext context, IRubyObject list) {
RubyRepeatedField dup = (RubyRepeatedField) dup(context); RubyRepeatedField dup = (RubyRepeatedField) dup(context);
if (list instanceof RubyArray) { if (list instanceof RubyArray) {
@ -231,6 +243,27 @@ public class RubyRepeatedField extends RubyObject {
return dup; return dup;
} }
/*
* call-seq:
* RepeatedField.concat(other) => self
*
* concats the passed in array to self. Returns a Ruby array.
*/
@JRubyMethod
public IRubyObject concat(ThreadContext context, IRubyObject list) {
if (list instanceof RubyArray) {
checkArrayElementType(context, (RubyArray) list);
this.storage.addAll((RubyArray) list);
} else {
RubyRepeatedField repeatedField = (RubyRepeatedField) list;
if (! fieldType.equals(repeatedField.fieldType) || (typeClass != null && !
typeClass.equals(repeatedField.typeClass)))
throw context.runtime.newArgumentError("Attempt to append RepeatedField with different element type.");
this.storage.addAll((RubyArray) repeatedField.toArray(context));
}
return this.storage;
}
/* /*
* call-seq: * call-seq:
* RepeatedField.hash => hash_value * RepeatedField.hash => hash_value
@ -239,7 +272,7 @@ public class RubyRepeatedField extends RubyObject {
*/ */
@JRubyMethod @JRubyMethod
public IRubyObject hash(ThreadContext context) { public IRubyObject hash(ThreadContext context) {
int hashCode = System.identityHashCode(this.storage); int hashCode = this.storage.hashCode();
return context.runtime.newFixnum(hashCode); return context.runtime.newFixnum(hashCode);
} }
@ -268,17 +301,12 @@ public class RubyRepeatedField extends RubyObject {
@JRubyMethod @JRubyMethod
public IRubyObject each(ThreadContext context, Block block) { public IRubyObject each(ThreadContext context, Block block) {
this.storage.each(context, block); this.storage.each(context, block);
return context.runtime.getNil(); return this.storage;
} }
@JRubyMethod(name = {"to_ary", "to_a"}) @JRubyMethod(name = {"to_ary", "to_a"})
public IRubyObject toArray(ThreadContext context) { public IRubyObject toArray(ThreadContext context) {
for (int i = 0; i < this.storage.size(); i++) {
IRubyObject defaultValue = defaultValue(context);
if (storage.eltInternal(i).isNil()) {
storage.set(i, defaultValue);
}
}
return this.storage; return this.storage;
} }
@ -298,31 +326,6 @@ public class RubyRepeatedField extends RubyObject {
return dup; return dup;
} }
/*
* call-seq:
* RepeatedField.inspect => string
*
* Returns a string representing this repeated field's elements. It will be
* formated as "[<element>, <element>, ...]", with each element's string
* representation computed by its own #inspect method.
*/
@JRubyMethod
public IRubyObject inspect() {
StringBuilder str = new StringBuilder("[");
for (int i = 0; i < this.storage.size(); i++) {
str.append(storage.eltInternal(i).inspect());
str.append(", ");
}
if (str.length() > 1) {
str.replace(str.length() - 2, str.length(), "]");
} else {
str.append("]");
}
return getRuntime().newString(str.toString());
}
// Java API // Java API
protected IRubyObject get(int index) { protected IRubyObject get(int index) {
return this.storage.eltInternal(index); return this.storage.eltInternal(index);
@ -376,6 +379,9 @@ public class RubyRepeatedField extends RubyObject {
case STRING: case STRING:
value = sentinel.getDefaultString(); value = sentinel.getDefaultString();
break; break;
case ENUM:
IRubyObject defaultEnumLoc = context.runtime.newFixnum(0);
return RubyEnum.lookup(context, typeClass, defaultEnumLoc);
default: default:
return context.runtime.getNil(); return context.runtime.getNil();
} }

@ -178,7 +178,7 @@ module BasicTest
:optional_msg => TestMessage2.new, :optional_msg => TestMessage2.new,
:repeated_string => ["hello", "there", "world"]) :repeated_string => ["hello", "there", "world"])
expected = '<BasicTest::TestMessage: optional_int32: -42, optional_int64: 0, optional_uint32: 0, optional_uint64: 0, optional_bool: false, optional_float: 0.0, optional_double: 0.0, optional_string: "", optional_bytes: "", optional_msg: <BasicTest::TestMessage2: foo: 0>, optional_enum: :A, repeated_int32: [], repeated_int64: [], repeated_uint32: [], repeated_uint64: [], repeated_bool: [], repeated_float: [], repeated_double: [], repeated_string: ["hello", "there", "world"], repeated_bytes: [], repeated_msg: [], repeated_enum: []>' expected = '<BasicTest::TestMessage: optional_int32: -42, optional_int64: 0, optional_uint32: 0, optional_uint64: 0, optional_bool: false, optional_float: 0.0, optional_double: 0.0, optional_string: "", optional_bytes: "", optional_msg: <BasicTest::TestMessage2: foo: 0>, optional_enum: :A, repeated_int32: [], repeated_int64: [], repeated_uint32: [], repeated_uint64: [], repeated_bool: [], repeated_float: [], repeated_double: [], repeated_string: ["hello", "there", "world"], repeated_bytes: [], repeated_msg: [], repeated_enum: []>'
assert m.inspect == expected assert_equal expected, m.inspect
end end
def test_hash def test_hash
@ -276,7 +276,7 @@ module BasicTest
assert l.inspect == '[5, 2, 3, 4]' assert l.inspect == '[5, 2, 3, 4]'
l.insert(7, 8, 9) l.concat([7, 8, 9])
assert l == [5, 2, 3, 4, 7, 8, 9] assert l == [5, 2, 3, 4, 7, 8, 9]
assert l.pop == 9 assert l.pop == 9
assert l == [5, 2, 3, 4, 7, 8] assert l == [5, 2, 3, 4, 7, 8]

@ -0,0 +1,640 @@
#!/usr/bin/ruby
require 'google/protobuf'
require 'test/unit'
class RepeatedFieldTest < Test::Unit::TestCase
def test_acts_like_enumerator
m = TestMessage.new
(Enumerable.instance_methods - TestMessage.new.repeated_string.methods).each do |method_name|
assert m.repeated_string.respond_to?(method_name) == true, "does not respond to #{method_name}"
end
end
def test_acts_like_an_array
m = TestMessage.new
arr_methods = ([].methods - TestMessage.new.repeated_string.methods)
# jRuby additions to the Array class that we can ignore
arr_methods -= [ :indices, :iter_for_each, :iter_for_each_index,
:iter_for_each_with_index, :dimensions, :copy_data, :copy_data_simple,
:nitems, :iter_for_reverse_each, :indexes]
arr_methods.each do |method_name|
assert m.repeated_string.respond_to?(method_name) == true, "does not respond to #{method_name}"
end
end
def test_first
m = TestMessage.new
repeated_field_names(TestMessage).each do |field_name|
assert_nil m.send(field_name).first
end
fill_test_msg(m)
assert_equal -10, m.repeated_int32.first
assert_equal -1_000_000, m.repeated_int64.first
assert_equal 10, m.repeated_uint32.first
assert_equal 1_000_000, m.repeated_uint64.first
assert_equal true, m.repeated_bool.first
assert_equal -1.01, m.repeated_float.first.round(2)
assert_equal -1.0000000000001, m.repeated_double.first
assert_equal 'foo', m.repeated_string.first
assert_equal "bar".encode!('ASCII-8BIT'), m.repeated_bytes.first
assert_equal TestMessage2.new(:foo => 1), m.repeated_msg.first
assert_equal :A, m.repeated_enum.first
end
def test_last
m = TestMessage.new
repeated_field_names(TestMessage).each do |field_name|
assert_nil m.send(field_name).first
end
fill_test_msg(m)
assert_equal -11, m.repeated_int32.last
assert_equal -1_000_001, m.repeated_int64.last
assert_equal 11, m.repeated_uint32.last
assert_equal 1_000_001, m.repeated_uint64.last
assert_equal false, m.repeated_bool.last
assert_equal -1.02, m.repeated_float.last.round(2)
assert_equal -1.0000000000002, m.repeated_double.last
assert_equal 'bar', m.repeated_string.last
assert_equal "foo".encode!('ASCII-8BIT'), m.repeated_bytes.last
assert_equal TestMessage2.new(:foo => 2), m.repeated_msg.last
assert_equal :B, m.repeated_enum.last
end
def test_pop
m = TestMessage.new
repeated_field_names(TestMessage).each do |field_name|
assert_nil m.send(field_name).pop
end
fill_test_msg(m)
assert_equal -11, m.repeated_int32.pop
assert_equal -10, m.repeated_int32.pop
assert_equal -1_000_001, m.repeated_int64.pop
assert_equal -1_000_000, m.repeated_int64.pop
assert_equal 11, m.repeated_uint32.pop
assert_equal 10, m.repeated_uint32.pop
assert_equal 1_000_001, m.repeated_uint64.pop
assert_equal 1_000_000, m.repeated_uint64.pop
assert_equal false, m.repeated_bool.pop
assert_equal true, m.repeated_bool.pop
assert_equal -1.02, m.repeated_float.pop.round(2)
assert_equal -1.01, m.repeated_float.pop.round(2)
assert_equal -1.0000000000002, m.repeated_double.pop
assert_equal -1.0000000000001, m.repeated_double.pop
assert_equal 'bar', m.repeated_string.pop
assert_equal 'foo', m.repeated_string.pop
assert_equal "foo".encode!('ASCII-8BIT'), m.repeated_bytes.pop
assert_equal "bar".encode!('ASCII-8BIT'), m.repeated_bytes.pop
assert_equal TestMessage2.new(:foo => 2), m.repeated_msg.pop
assert_equal TestMessage2.new(:foo => 1), m.repeated_msg.pop
assert_equal :B, m.repeated_enum.pop
assert_equal :A, m.repeated_enum.pop
repeated_field_names(TestMessage).each do |field_name|
assert_nil m.send(field_name).pop
end
fill_test_msg(m)
assert_equal ['bar', 'foo'], m.repeated_string.pop(2)
assert_nil m.repeated_string.pop
end
def test_each
m = TestMessage.new
5.times{|i| m.repeated_string << 'string' }
count = 0
m.repeated_string.each do |val|
assert_equal 'string', val
count += 1
end
assert_equal 5, count
result = m.repeated_string.each{|val| val + '_junk'}
assert_equal ['string'] * 5, result
end
def test_empty?
m = TestMessage.new
assert_equal true, m.repeated_string.empty?
m.repeated_string << 'foo'
assert_equal false, m.repeated_string.empty?
m.repeated_string << 'bar'
assert_equal false, m.repeated_string.empty?
end
def test_array_accessor
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr[1]
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr[-2]
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr[20]
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr[1, 2]
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr[0..2]
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr[-1, 1]
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr[10, 12]
end
end
def test_array_settor
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr[1] = 'junk'
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr[-2] = 'snappy'
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr[3] = ''
end
# slight deviation; we are strongly typed, and nil is not allowed
# for string types;
m.repeated_string[5] = 'spacious'
assert_equal ["foo", "snappy", "baz", "", "", "spacious"], m.repeated_string
#make sure it sests the default types for other fields besides strings
%w(repeated_int32 repeated_int64 repeated_uint32 repeated_uint64).each do |field_name|
m.send(field_name)[3] = 10
assert_equal [0,0,0,10], m.send(field_name)
end
m.repeated_float[3] = 10.1
#wonky mri float handling
assert_equal [0,0,0], m.repeated_float.to_a[0..2]
assert_equal 10.1, m.repeated_float[3].round(1)
m.repeated_double[3] = 10.1
assert_equal [0,0,0,10.1], m.repeated_double
m.repeated_bool[3] = true
assert_equal [false, false, false, true], m.repeated_bool
m.repeated_bytes[3] = "bar".encode!('ASCII-8BIT')
assert_equal ['', '', '', "bar".encode!('ASCII-8BIT')], m.repeated_bytes
m.repeated_msg[3] = TestMessage2.new(:foo => 1)
assert_equal [nil, nil, nil, TestMessage2.new(:foo => 1)], m.repeated_msg
m.repeated_enum[3] = :A
assert_equal [:Default, :Default, :Default, :A], m.repeated_enum
# check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
# arr[20] = 'spacious'
# end
# TODO: accessor doesn't allow other ruby-like methods
# check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
# arr[1, 2] = 'fizz'
# end
# check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
# arr[0..2] = 'buzz'
# end
end
def test_push
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.push('fizz')
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr << 'fizz'
end
#TODO: push should support multiple
# check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
# arr.push('fizz', 'buzz')
# end
end
def test_clear
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.clear
end
end
def test_concat
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
m.repeated_string.concat(['fizz', 'buzz'])
assert_equal %w(foo bar baz fizz buzz), m.repeated_string
#TODO: concat should return the orig array
# check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
# arr.concat(['fizz', 'buzz'])
# end
end
def test_equal
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
assert_equal reference_arr, m.repeated_string
reference_arr << 'fizz'
assert_not_equal reference_arr, m.repeated_string
m.repeated_string << 'fizz'
assert_equal reference_arr, m.repeated_string
end
def test_hash
# just a sanity check
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
assert m.repeated_string.hash.is_a?(Integer)
hash = m.repeated_string.hash
assert_equal hash, m.repeated_string.hash
m.repeated_string << 'j'
assert_not_equal hash, m.repeated_string.hash
end
def test_plus
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr + ['fizz', 'buzz']
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr += ['fizz', 'buzz']
end
end
def test_replace
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.replace(['fizz', 'buzz'])
end
end
def test_to_a
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.to_a
end
end
def test_to_ary
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.to_ary
end
end
# emulate Array behavior
##########################
def test_collect!
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.collect!{|x| x + "!" }
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.collect!.with_index{|x, i| x[0...i] }
end
end
def test_compact!
m = TestMessage.new
m.repeated_msg << TestMessage2.new(:foo => 1)
m.repeated_msg << nil
m.repeated_msg << TestMessage2.new(:foo => 2)
reference_arr = m.repeated_string.to_a
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.compact!
end
end
def test_delete
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.delete('bar')
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.delete('nope')
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.delete('nope'){'within'}
end
end
def test_delete_at
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.delete_at(2)
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.delete_at(10)
end
end
def test_fill
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.fill("x")
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.fill("z", 2, 2)
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.fill("y", 0..1)
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.fill { |i| (i*i).to_s }
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.fill(-2) { |i| (i*i*i).to_s }
end
end
def test_flatten!
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.flatten!
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.flatten!(1)
end
end
def test_insert
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.insert(2, 'fizz')
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.insert(3, 'fizz', 'buzz', 'bazz')
end
end
def test_inspect
m = TestMessage.new
assert_equal '[]', m.repeated_string.inspect
m.repeated_string << 'foo'
assert_equal m.repeated_string.to_a.inspect, m.repeated_string.inspect
m.repeated_string << 'bar'
assert_equal m.repeated_string.to_a.inspect, m.repeated_string.inspect
end
def test_reverse!
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.reverse!
end
end
def test_rotate!
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.rotate!
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.rotate!(2)
end
end
def test_select!
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.select! { |v| v =~ /[aeiou]/ }
end
end
def test_shift
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
# should return an element
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.shift
end
# should return an array
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.shift(2)
end
# should return nil
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.shift
end
end
def test_shuffle!
m = TestMessage.new
m.repeated_string += %w(foo bar baz)
orig_repeated_string = m.repeated_string.clone
result = m.repeated_string.shuffle!
assert_equal m.repeated_string, result
# NOTE: sometimes it doesn't change the order...
# assert_not_equal m.repeated_string.to_a, orig_repeated_string.to_a
end
def test_slice!
m = TestMessage.new
reference_arr = %w(foo bar baz bar fizz buzz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.slice!(2)
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.slice!(1,2)
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.slice!(0..1)
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.slice!(10)
end
end
def test_sort!
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.sort!
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.sort! { |x,y| y <=> x }
end
end
def test_sort_by!
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.sort_by!
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.sort_by!(&:hash)
end
end
def test_uniq!
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.uniq!
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.uniq!{|s| s[0] }
end
end
def test_unshift
m = TestMessage.new
reference_arr = %w(foo bar baz)
m.repeated_string += reference_arr.clone
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.unshift('1')
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.unshift('a', 'b')
end
check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
arr.unshift('')
end
end
##### HELPER METHODS
def check_self_modifying_method(repeated_field, ref_array)
expected_result = yield(ref_array)
actual_result = yield(repeated_field)
if expected_result.is_a?(Enumerator)
assert_equal expected_result.to_a, actual_result.to_a
else
assert_equal expected_result, actual_result
end
assert_equal ref_array, repeated_field
end
def repeated_field_names(klass)
klass.descriptor.find_all{|f| f.label == :repeated}.map(&:name)
end
def fill_test_msg(test_msg)
test_msg.repeated_int32 += [-10, -11]
test_msg.repeated_int64 += [-1_000_000, -1_000_001]
test_msg.repeated_uint32 += [10, 11]
test_msg.repeated_uint64 += [1_000_000, 1_000_001]
test_msg.repeated_bool += [true, false]
test_msg.repeated_float += [-1.01, -1.02]
test_msg.repeated_double += [-1.0000000000001, -1.0000000000002]
test_msg.repeated_string += %w(foo bar)
test_msg.repeated_bytes += ["bar".encode!('ASCII-8BIT'), "foo".encode!('ASCII-8BIT')]
test_msg.repeated_msg << TestMessage2.new(:foo => 1)
test_msg.repeated_msg << TestMessage2.new(:foo => 2)
test_msg.repeated_enum << :A
test_msg.repeated_enum << :B
end
pool = Google::Protobuf::DescriptorPool.new
pool.build do
add_message "TestMessage" do
optional :optional_int32, :int32, 1
optional :optional_int64, :int64, 2
optional :optional_uint32, :uint32, 3
optional :optional_uint64, :uint64, 4
optional :optional_bool, :bool, 5
optional :optional_float, :float, 6
optional :optional_double, :double, 7
optional :optional_string, :string, 8
optional :optional_bytes, :bytes, 9
optional :optional_msg, :message, 10, "TestMessage2"
optional :optional_enum, :enum, 11, "TestEnum"
repeated :repeated_int32, :int32, 12
repeated :repeated_int64, :int64, 13
repeated :repeated_uint32, :uint32, 14
repeated :repeated_uint64, :uint64, 15
repeated :repeated_bool, :bool, 16
repeated :repeated_float, :float, 17
repeated :repeated_double, :double, 18
repeated :repeated_string, :string, 19
repeated :repeated_bytes, :bytes, 20
repeated :repeated_msg, :message, 21, "TestMessage2"
repeated :repeated_enum, :enum, 22, "TestEnum"
end
add_message "TestMessage2" do
optional :foo, :int32, 1
end
add_enum "TestEnum" do
value :Default, 0
value :A, 1
value :B, 2
value :C, 3
end
end
TestMessage = pool.lookup("TestMessage").msgclass
TestMessage2 = pool.lookup("TestMessage2").msgclass
TestEnum = pool.lookup("TestEnum").enummodule
end
Loading…
Cancel
Save