From 0dccf10db7f8255c0dcb6d0142314bc62e34875a Mon Sep 17 00:00:00 2001 From: Alexander Polcyn Date: Mon, 27 Jun 2016 13:11:07 -0700 Subject: [PATCH 01/10] Added ruby wrapper for grpc_compression_options --- src/ruby/ext/grpc/rb_compression_options.c | 351 ++++++++++++++++++ src/ruby/ext/grpc/rb_compression_options.h | 44 +++ src/ruby/ext/grpc/rb_grpc.c | 2 + src/ruby/lib/grpc.rb | 1 + src/ruby/lib/grpc/core/compression_options.rb | 93 +++++ src/ruby/spec/compression_options_spec.rb | 254 +++++++++++++ 6 files changed, 745 insertions(+) create mode 100644 src/ruby/ext/grpc/rb_compression_options.c create mode 100644 src/ruby/ext/grpc/rb_compression_options.h create mode 100644 src/ruby/lib/grpc/core/compression_options.rb create mode 100644 src/ruby/spec/compression_options_spec.rb diff --git a/src/ruby/ext/grpc/rb_compression_options.c b/src/ruby/ext/grpc/rb_compression_options.c new file mode 100644 index 00000000000..66ba8fb84aa --- /dev/null +++ b/src/ruby/ext/grpc/rb_compression_options.c @@ -0,0 +1,351 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include + +#include "rb_compression_options.h" +#include "rb_grpc_imports.generated.h" + +#include +#include +#include +#include +#include +#include + +#include "rb_grpc.h" + +static VALUE grpc_rb_cCompressionOptions = Qnil; + +/* grpc_rb_compression_options wraps a grpc_compression_options. + * Note that ruby objects of this type don't carry any state in other + * Ruby objects and don't have a mark for GC. */ +typedef struct grpc_rb_compression_options { + /* The actual compression options that's being wrapped */ + grpc_compression_options *wrapped; +} grpc_rb_compression_options; + +/* Destroys the compression options instances and free the + * wrapped grpc compression options. */ +static void grpc_rb_compression_options_free(void *p) { + grpc_rb_compression_options *wrapper = NULL; + if (p == NULL) { + return; + }; + wrapper = (grpc_rb_compression_options *)p; + + if (wrapper->wrapped != NULL) { + gpr_free(wrapper->wrapped); + wrapper->wrapped = NULL; + } + + xfree(p); +} + +/* Ruby recognized data type for the CompressionOptions class. */ +static rb_data_type_t grpc_rb_compression_options_data_type = { + "grpc_compression_options", + {NULL, + grpc_rb_compression_options_free, + GRPC_RB_MEMSIZE_UNAVAILABLE, + {NULL, NULL}}, + NULL, + NULL, +#ifdef RUBY_TYPED_FREE_IMMEDIATELY + RUBY_TYPED_FREE_IMMEDIATELY +#endif +}; + +/* Allocates CompressionOptions instances. + Allocate the wrapped grpc compression options and + initialize it here too. */ +static VALUE grpc_rb_compression_options_alloc(VALUE cls) { + grpc_rb_compression_options *wrapper = + gpr_malloc(sizeof(grpc_rb_compression_options)); + wrapper->wrapped = NULL; + wrapper->wrapped = gpr_malloc(sizeof(grpc_compression_options)); + grpc_compression_options_init(wrapper->wrapped); + + return TypedData_Wrap_Struct(cls, &grpc_rb_compression_options_data_type, + wrapper); +} + +/* Disables a compression algorithm, given the GRPC core internal number of a + * compression algorithm. */ +VALUE grpc_rb_compression_options_disable_compression_algorithm_internal( + VALUE self, VALUE algorithm_to_disable) { + grpc_compression_algorithm compression_algorithm = 0; + grpc_rb_compression_options *wrapper = NULL; + + TypedData_Get_Struct(self, grpc_rb_compression_options, + &grpc_rb_compression_options_data_type, wrapper); + compression_algorithm = + (grpc_compression_algorithm)NUM2INT(algorithm_to_disable); + + grpc_compression_options_disable_algorithm(wrapper->wrapped, + compression_algorithm); + + return Qnil; +} + +/* Provides a bitset as a ruby number that is suitable to pass to + * the GRPC core as a channel argument to enable compression algorithms. */ +VALUE grpc_rb_compression_options_get_enabled_algorithms_bitset(VALUE self) { + grpc_rb_compression_options *wrapper = NULL; + + TypedData_Get_Struct(self, grpc_rb_compression_options, + &grpc_rb_compression_options_data_type, wrapper); + return INT2NUM((int)wrapper->wrapped->enabled_algorithms_bitset); +} + +void grpc_rb_compression_options_set_default_level_helper( + grpc_compression_options *compression_options, + grpc_compression_level level) { + compression_options->default_level.is_set |= 1; + compression_options->default_level.level = level; +} + +/* Sets the default compression level, given the name of a compression level. + * Throws an error if no algorithm matched. */ +VALUE grpc_rb_compression_options_set_default_level(VALUE self, + VALUE new_level) { + char *level_name = NULL; + grpc_rb_compression_options *wrapper = NULL; + long name_len = 0; + VALUE ruby_str = Qnil; + + TypedData_Get_Struct(self, grpc_rb_compression_options, + &grpc_rb_compression_options_data_type, wrapper); + + /* Take both string and symbol parameters */ + ruby_str = rb_funcall(new_level, rb_intern("to_s"), 0); + + level_name = RSTRING_PTR(ruby_str); + name_len = RSTRING_LEN(ruby_str); + + /* Check the compression level of the name passed in, and see which macro + * from the GRPC core header files match. */ + if (strncmp(level_name, "none", name_len) == 0) { + grpc_rb_compression_options_set_default_level_helper( + wrapper->wrapped, GRPC_COMPRESS_LEVEL_NONE); + } else if (strncmp(level_name, "low", name_len) == 0) { + grpc_rb_compression_options_set_default_level_helper( + wrapper->wrapped, GRPC_COMPRESS_LEVEL_LOW); + } else if (strncmp(level_name, "medium", name_len) == 0) { + grpc_rb_compression_options_set_default_level_helper( + wrapper->wrapped, GRPC_COMPRESS_LEVEL_MED); + } else if (strncmp(level_name, "high", name_len) == 0) { + grpc_rb_compression_options_set_default_level_helper( + wrapper->wrapped, GRPC_COMPRESS_LEVEL_HIGH); + } else { + rb_raise(rb_eNameError, + "Invalid compression level name. Supported levels: none, low, " + "medium, high"); + } + + return Qnil; +} + +/* Gets the internal value of a compression algorithm suitable as the value + * in a GRPC core channel arguments hash. + * Raises an error if the name of the algorithm passed in is invalid. */ +void grpc_rb_compression_options_get_internal_value_of_algorithm( + VALUE algorithm_name, grpc_compression_algorithm *compression_algorithm) { + VALUE ruby_str = Qnil; + char *name_str = NULL; + long name_len = 0; + + /* Accept ruby symbol and string parameters. */ + ruby_str = rb_funcall(algorithm_name, rb_intern("to_s"), 0); + name_str = RSTRING_PTR(ruby_str); + name_len = RSTRING_LEN(ruby_str); + + /* Raise an error if the name isn't recognized as a compression algorithm by + * the algorithm parse function + * in GRPC core. */ + if (!grpc_compression_algorithm_parse(name_str, name_len, + compression_algorithm)) { + rb_raise(rb_eNameError, + "Invalid compression algorithm name."); + } +} + +/* Sets the default algorithm to the name of the algorithm passed in. + * Raises an error if the name is not a valid compression algorithm name. */ +VALUE grpc_rb_compression_options_set_default_algorithm(VALUE self, + VALUE algorithm_name) { + grpc_rb_compression_options *wrapper = NULL; + + TypedData_Get_Struct(self, grpc_rb_compression_options, + &grpc_rb_compression_options_data_type, wrapper); + + grpc_rb_compression_options_get_internal_value_of_algorithm( + algorithm_name, &wrapper->wrapped->default_algorithm.algorithm); + wrapper->wrapped->default_algorithm.is_set |= 1; + + return Qnil; +} + +/* Gets the internal value of the default compression level that is to be passed + * to the + * the GRPC core as a channel argument value. + * A nil return value means that it hasn't been set. */ +VALUE grpc_rb_compression_options_default_algorithm_internal_value(VALUE self) { + grpc_rb_compression_options *wrapper = NULL; + + TypedData_Get_Struct(self, grpc_rb_compression_options, + &grpc_rb_compression_options_data_type, wrapper); + + if (wrapper->wrapped->default_algorithm.is_set) { + return INT2NUM(wrapper->wrapped->default_algorithm.algorithm); + } else { + return Qnil; + } +} + +/* Gets the internal value of the default compression level that is to be passed + * to the GRPC core as a channel argument value. + * A nil return value means that it hasn't been set. */ +VALUE grpc_rb_compression_options_default_level_internal_value(VALUE self) { + grpc_rb_compression_options *wrapper = NULL; + + TypedData_Get_Struct(self, grpc_rb_compression_options, + &grpc_rb_compression_options_data_type, wrapper); + + if (wrapper->wrapped->default_level.is_set) { + return INT2NUM((int)wrapper->wrapped->default_level.level); + } else { + return Qnil; + } +} + +/* Disables compression algorithms by their names. Raises an error if an unkown + * name was passed. */ +VALUE grpc_rb_compression_options_disable_algorithms(int argc, VALUE *argv, + VALUE self) { + VALUE algorithm_names = Qnil; + VALUE ruby_str = Qnil; + grpc_compression_algorithm internal_algorithm_value; + + /* read variadic argument list of names into the algorithm_name ruby array. */ + rb_scan_args(argc, argv, "0*", &algorithm_names); + + for (int i = 0; i < RARRAY_LEN(algorithm_names); i++) { + ruby_str = + rb_funcall(rb_ary_entry(algorithm_names, i), rb_intern("to_s"), 0); + grpc_rb_compression_options_get_internal_value_of_algorithm( + ruby_str, &internal_algorithm_value); + rb_funcall(self, rb_intern("disable_algorithm_internal"), 1, + LONG2NUM((long)internal_algorithm_value)); + } + + return Qnil; +} + +/* Provides a ruby hash of GRPC core channel argument key-values that + * correspond to the compression settings on this instance. */ +VALUE grpc_rb_compression_options_to_hash(VALUE self) { + grpc_rb_compression_options *wrapper = NULL; + grpc_compression_options *compression_options = NULL; + VALUE channel_arg_hash = rb_funcall(rb_cHash, rb_intern("new"), 0); + + TypedData_Get_Struct(self, grpc_rb_compression_options, + &grpc_rb_compression_options_data_type, wrapper); + compression_options = wrapper->wrapped; + + /* Add key-value pairs to the new Ruby hash. It can be used + * as GRPC core channel arguments. */ + if (compression_options->default_level.is_set) { + rb_funcall(channel_arg_hash, rb_intern("[]="), 2, + rb_str_new2(GRPC_COMPRESSION_CHANNEL_DEFAULT_LEVEL), + INT2NUM((int)compression_options->default_level.level)); + } + + if (compression_options->default_algorithm.is_set) { + rb_funcall(channel_arg_hash, rb_intern("[]="), 2, + rb_str_new2(GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM), + INT2NUM((int)compression_options->default_algorithm.algorithm)); + } + + rb_funcall(channel_arg_hash, rb_intern("[]="), 2, + rb_str_new2(GRPC_COMPRESSION_CHANNEL_ENABLED_ALGORITHMS_BITSET), + INT2NUM((int)compression_options->enabled_algorithms_bitset)); + + return channel_arg_hash; +} + +void Init_grpc_compression_options() { + grpc_rb_cCompressionOptions = rb_define_class_under( + grpc_rb_mGrpcCore, "CompressionOptions", rb_cObject); + + /* Allocates an object managed by the ruby runtime. */ + rb_define_alloc_func(grpc_rb_cCompressionOptions, + grpc_rb_compression_options_alloc); + + /* Private method for disabling algorithms by a variadic list of names. */ + rb_define_private_method(grpc_rb_cCompressionOptions, "disable_algorithms", + grpc_rb_compression_options_disable_algorithms, -1); + /* Private method for disabling an algorithm by its enum value. */ + rb_define_private_method( + grpc_rb_cCompressionOptions, "disable_algorithm_internal", + grpc_rb_compression_options_disable_compression_algorithm_internal, 1); + + /* Private method for getting the bitset of enabled algorithms. */ + rb_define_private_method( + grpc_rb_cCompressionOptions, "enabled_algorithms_bitset", + grpc_rb_compression_options_get_enabled_algorithms_bitset, 0); + + /* Private method for setting the default algorithm by name. */ + rb_define_private_method(grpc_rb_cCompressionOptions, "set_default_algorithm", + grpc_rb_compression_options_set_default_algorithm, + 1); + /* Private method for getting the internal enum value of the default + * algorithm. */ + rb_define_private_method( + grpc_rb_cCompressionOptions, "default_algorithm_internal_value", + grpc_rb_compression_options_default_algorithm_internal_value, 0); + + /* Private method for setting the default compression level by name. */ + rb_define_private_method(grpc_rb_cCompressionOptions, "set_default_level", + grpc_rb_compression_options_set_default_level, 1); + + /* Private method for getting the internal enum value of the default level. */ + rb_define_private_method( + grpc_rb_cCompressionOptions, "default_level_internal_value", + grpc_rb_compression_options_default_level_internal_value, 0); + + /* Public method for returning a hash of the compression settings suitable + * for passing to server or channel args. */ + rb_define_method(grpc_rb_cCompressionOptions, "to_hash", + grpc_rb_compression_options_to_hash, 0); +} diff --git a/src/ruby/ext/grpc/rb_compression_options.h b/src/ruby/ext/grpc/rb_compression_options.h new file mode 100644 index 00000000000..4d5a9247864 --- /dev/null +++ b/src/ruby/ext/grpc/rb_compression_options.h @@ -0,0 +1,44 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_RB_COMPRESSION_OPTIONS_H_ +#define GRPC_RB_COMPRESSION_OPTIONS_H_ + +#include + +#include + +/* Initializes the compression options ruby wrapper. */ +void Init_grpc_compression_options(); + +#endif /* GRPC_RB_COMPRESSION_OPTIONS_H_ */ diff --git a/src/ruby/ext/grpc/rb_grpc.c b/src/ruby/ext/grpc/rb_grpc.c index 188a62475d2..525508dbb19 100644 --- a/src/ruby/ext/grpc/rb_grpc.c +++ b/src/ruby/ext/grpc/rb_grpc.c @@ -49,6 +49,7 @@ #include "rb_loader.h" #include "rb_server.h" #include "rb_server_credentials.h" +#include "rb_compression_options.h" static VALUE grpc_rb_cTimeVal = Qnil; @@ -332,4 +333,5 @@ void Init_grpc_c() { Init_grpc_server_credentials(); Init_grpc_status_codes(); Init_grpc_time_consts(); + Init_grpc_compression_options(); } diff --git a/src/ruby/lib/grpc.rb b/src/ruby/lib/grpc.rb index 79fa705b1c9..2dae3a64d61 100644 --- a/src/ruby/lib/grpc.rb +++ b/src/ruby/lib/grpc.rb @@ -35,6 +35,7 @@ require_relative 'grpc/logconfig' require_relative 'grpc/notifier' require_relative 'grpc/version' require_relative 'grpc/core/time_consts' +require_relative 'grpc/core/compression_options' require_relative 'grpc/generic/active_call' require_relative 'grpc/generic/client_stub' require_relative 'grpc/generic/service' diff --git a/src/ruby/lib/grpc/core/compression_options.rb b/src/ruby/lib/grpc/core/compression_options.rb new file mode 100644 index 00000000000..757c69f8e05 --- /dev/null +++ b/src/ruby/lib/grpc/core/compression_options.rb @@ -0,0 +1,93 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +require_relative '../grpc' + +# GRPC contains the General RPC module. +module GRPC + module Core + # Wrapper for grpc_compression_options in core + # This class is defined as a C extension but is reopened here + # to add the initialization logic. + # + # This class wraps a GRPC core compression options. + # + # It can be used to create a channel argument key-value hash + # with keys and values that correspond to the compression settings + # provided here. + # + # call-seq: + # options = CompressionOptions.new( + # default_level: low, + # disabled_algorithms: [:]) + # + # channel_args = Hash.new[...] + # channel_args_with_compression_args = channel_args.merge(options) + class CompressionOptions + alias_method :to_channel_arg_hash, :to_hash + + # Initializes a CompresionOptions instance. + # Starts out with all available compression + # algorithms enabled by default. + # + # Valid algorithms are those supported by the GRPC core + # + # @param default_level [String | Symbol] + # one of 'none', 'low', 'medium', 'high' + # @param default_algorithm [String | Symbol] + # a valid GRPC algorithm + # @param disabled_algorithms [Array] + # can contain valid GRPC algorithm names + def initialize(default_algorithm: nil, + default_level: nil, + disabled_algorithms: []) + # Convert possible symbols to strings for comparisons + disabled_algorithms = disabled_algorithms.map(&:to_s) + + if disabled_algorithms.include?(default_algorithm.to_s) + fail ArgumentError("#{default_algorithm} is in disabled_algorithms") + end + + set_default_algorithm(default_algorithm.to_s) unless + default_algorithm.nil? + + set_default_level(default_level.to_s) unless + default_level.nil? + + # *disabled_algorithms spreads array into variadic method parameters + disable_algorithms(*disabled_algorithms) unless + disabled_algorithms.nil? || disabled_algorithms.empty? + end + + def to_s + to_hash.to_s + end + end + end +end diff --git a/src/ruby/spec/compression_options_spec.rb b/src/ruby/spec/compression_options_spec.rb new file mode 100644 index 00000000000..32c7a1bc943 --- /dev/null +++ b/src/ruby/spec/compression_options_spec.rb @@ -0,0 +1,254 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +require 'grpc' + +describe GRPC::Core::CompressionOptions do + # Names of supported compression algorithms and their internal enum values + ALGORITHMS = { + identity: 0, + deflate: 1, + gzip: 2 + } + + # Compression algorithms and their corresponding bits in the internal + # enabled algorithms bitset for GRPC core channel args. + ALGORITHM_BITS = { + identity: 0x1, + deflate: 0x2, + gzip: 0x4 + } + + # "enabled algorithms bitset" when all compression algorithms are enabled + ALL_ENABLED_BITSET = 0x7 + + # "enabled algorithms bitset" when all compression algorithms are disabled + ALL_DISABLED_BITSET = 0x0 + + # Names of valid supported compression levels and their internal enum values + COMPRESS_LEVELS = { + none: 0, + low: 1, + medium: 2, + high: 3 + } + + it 'implements to_s' do + expect { GRPC::Core::CompressionOptions.new.to_s }.to_not raise_error + end + + it '#to_channel_arg_hash gives the same result as #to_hash' do + options = GRPC::Core::CompressionOptions.new + expect(options.to_channel_arg_hash).to eql(options.to_hash) + end + + # Test the normal call sequence of creating an instance + # and then obtaining the resulting channel-arg hash that + # corresponds to the compression settings of the instance + describe 'creating and converting to channel args hash' do + it 'gives the correct channel args when nothing has been adjusted yet' do + expect(GRPC::Core::CompressionOptions.new.to_hash).to( + eql('grpc.compression_enabled_algorithms_bitset' => 0x7)) + end + + it 'gives the correct channel args after everything has been disabled' do + options = GRPC::Core::CompressionOptions.new( + default_algorithm: 'identity', + default_level: 'none', + disabled_algorithms: [:gzip, :deflate] + ) + + expect(options.to_hash).to( + eql('grpc.default_compression_algorithm' => 0, + 'grpc.default_compression_level' => 0, + 'grpc.compression_enabled_algorithms_bitset' => 0x1) + ) + end + + it 'gives correct channel args with all args set' do + options = GRPC::Core::CompressionOptions.new( + default_algorithm: 'gzip', + default_level: 'low', + disabled_algorithms: ['deflate'] + ) + + expected_bitset = ALL_ENABLED_BITSET & ~ALGORITHM_BITS[:deflate] + + expect(options.to_hash).to( + eql('grpc.default_compression_algorithm' => ALGORITHMS[:gzip], + 'grpc.default_compression_level' => COMPRESS_LEVELS[:low], + 'grpc.compression_enabled_algorithms_bitset' => expected_bitset) + ) + end + + it 'gives correct channel args when no algorithms are disabled' do + options = GRPC::Core::CompressionOptions.new( + default_algorithm: 'identity', + default_level: 'high' + ) + + expect(options.to_hash).to( + eql('grpc.default_compression_algorithm' => ALGORITHMS[:identity], + 'grpc.default_compression_level' => COMPRESS_LEVELS[:high], + 'grpc.compression_enabled_algorithms_bitset' => ALL_ENABLED_BITSET) + ) + end + + # Raising an error in when attempting to set the default algorithm + # to something that is also requested to be disabled + it 'gives raises an error when the chosen default algorithm is disabled' do + blk = proc do + GRPC::Core::CompressionOptions.new( + default_algorithm: :gzip, + disabled_algorithms: [:gzip]) + end + expect { blk.call }.to raise_error + end + end + + # Test the private methods in the C extension that interact with + # the wrapped grpc_compression_options. + # + # Using #send to call private methods. + describe 'private internal methods' do + it 'mutating functions and accessors should be private' do + options = GRPC::Core::CompressionOptions.new + + [:disable_algorithm_internal, + :disable_algorithms, + :set_default_algorithm, + :set_default_level, + :default_algorithm_internal_value, + :default_level_internal_value].each do |method_name| + expect(options.private_methods).to include(method_name) + end + end + + describe '#disable_algorithms' do + ALGORITHMS.each_pair do |name, internal_value| + it "passes #{internal_value} to internal method for #{name}" do + options = GRPC::Core::CompressionOptions.new + expect(options).to receive(:disable_algorithm_internal) + .with(internal_value) + + options.send(:disable_algorithms, name) + end + end + + it 'should work with multiple parameters' do + options = GRPC::Core::CompressionOptions.new + + ALGORITHMS.values do |internal_value| + expect(options).to receive(:disable_algorithm_internal) + .with(internal_value) + end + + # disabled_algorithms is a private, variadic method + options.send(:disable_algorithms, *ALGORITHMS.keys) + end + end + + describe '#new default values' do + it 'should start out with all algorithms enabled' do + options = GRPC::Core::CompressionOptions.new + bitset = options.send(:enabled_algorithms_bitset) + expect(bitset).to eql(ALL_ENABLED_BITSET) + end + + it 'should start out with no default algorithm' do + options = GRPC::Core::CompressionOptions.new + expect(options.send(:default_algorithm_internal_value)).to be_nil + end + + it 'should start out with no default level' do + options = GRPC::Core::CompressionOptions.new + expect(options.send(:default_level_internal_value)).to be_nil + end + end + + describe '#enabled_algoritms_bitset' do + it 'should respond to disabling one algorithm' do + options = GRPC::Core::CompressionOptions.new + options.send(:disable_algorithms, :gzip) + current_bitset = options.send(:enabled_algorithms_bitset) + expect(current_bitset & ALGORITHM_BITS[:gzip]).to be_zero + end + + it 'should respond to disabling multiple algorithms' do + options = GRPC::Core::CompressionOptions.new + + # splitting up algorithms array since #disable_algorithms is variadic + options.send(:disable_algorithms, *ALGORITHMS.keys) + current_bitset = options.send(:enabled_algorithms_bitset) + expect(current_bitset).to eql(ALL_DISABLED_BITSET) + end + end + + describe 'setting the default algorithm by name' do + it 'should set the internal value of the default algorithm' do + ALGORITHMS.each_pair do |name, expected_internal_value| + options = GRPC::Core::CompressionOptions.new + options.send(:set_default_algorithm, name) + internal_value = options.send(:default_algorithm_internal_value) + expect(internal_value).to eql(expected_internal_value) + end + end + + it 'should fail with invalid algorithm names' do + [:none, :low, :huffman, :unkown, Object.new, 1].each do |name| + blk = proc do + options = GRPC::CoreCompressionOptions.new + options.send(:set_default_algorithm, name) + end + expect { blk.call }.to raise_error + end + end + end + + describe 'setting the default level by name' do + it 'should set the internal value of the default compression value' do + COMPRESS_LEVELS.each_pair do |level, expected_internal_value| + options = GRPC::Core::CompressionOptions.new + options.send(:set_default_level, level) + internal_value = options.send(:default_level_internal_value) + expect(internal_value).to eql(expected_internal_value) + end + end + + it 'should fail with invalid names' do + [:identity, :gzip, :unkown, :any, Object.new, 1].each do |name| + blk = proc do + GRPC::Core::CompressionOptions.new.send(:set_default_level, name) + end + expect { blk.call }.to raise_error + end + end + end + end +end From d788b458cf8e229aad25afc04ad5ec85e24537a3 Mon Sep 17 00:00:00 2001 From: Alexander Polcyn Date: Wed, 6 Jul 2016 13:58:09 -0700 Subject: [PATCH 02/10] Replaced private ruby methods with internal c functions --- src/ruby/ext/grpc/rb_compression_options.c | 528 +++++++++++++----- src/ruby/lib/grpc.rb | 1 - src/ruby/lib/grpc/core/compression_options.rb | 93 --- src/ruby/spec/compression_options_spec.rb | 266 +++++---- 4 files changed, 543 insertions(+), 345 deletions(-) delete mode 100644 src/ruby/lib/grpc/core/compression_options.rb diff --git a/src/ruby/ext/grpc/rb_compression_options.c b/src/ruby/ext/grpc/rb_compression_options.c index 66ba8fb84aa..2d3be4c7d07 100644 --- a/src/ruby/ext/grpc/rb_compression_options.c +++ b/src/ruby/ext/grpc/rb_compression_options.c @@ -48,7 +48,10 @@ static VALUE grpc_rb_cCompressionOptions = Qnil; /* grpc_rb_compression_options wraps a grpc_compression_options. - * Note that ruby objects of this type don't carry any state in other + * It can be used to get the channel argument key-values for specific + * compression settings. */ + +/* Note that ruby objects of this type don't carry any state in other * Ruby objects and don't have a mark for GC. */ typedef struct grpc_rb_compression_options { /* The actual compression options that's being wrapped */ @@ -120,107 +123,242 @@ VALUE grpc_rb_compression_options_disable_compression_algorithm_internal( /* Provides a bitset as a ruby number that is suitable to pass to * the GRPC core as a channel argument to enable compression algorithms. */ -VALUE grpc_rb_compression_options_get_enabled_algorithms_bitset(VALUE self) { - grpc_rb_compression_options *wrapper = NULL; +/* Gets the compression internal enum value of a compression level given its + * name. */ +grpc_compression_level grpc_rb_compression_options_level_name_to_value_internal( + VALUE level_name) { + VALUE none_symbol = Qnil; + VALUE low_symbol = Qnil; + VALUE medium_symbol = Qnil; + VALUE high_symbol = Qnil; + + Check_Type(level_name, T_SYMBOL); + + /* Ruby symbols that correspond to names of valid compression levels */ + none_symbol = + rb_const_get(grpc_rb_cCompressionOptions, rb_intern("COMPRESS_NONE_SYM")); + low_symbol = + rb_const_get(grpc_rb_cCompressionOptions, rb_intern("COMPRESS_LOW_SYM")); + medium_symbol = rb_const_get(grpc_rb_cCompressionOptions, + rb_intern("COMPRESS_MEDIUM_SYM")); + high_symbol = + rb_const_get(grpc_rb_cCompressionOptions, rb_intern("COMPRESS_HIGH_SYM")); - TypedData_Get_Struct(self, grpc_rb_compression_options, - &grpc_rb_compression_options_data_type, wrapper); - return INT2NUM((int)wrapper->wrapped->enabled_algorithms_bitset); + /* Check the compression level of the name passed in, and see which macro + * from the GRPC core header files match. */ + if (RTEST(rb_funcall(level_name, rb_intern("=="), 1, none_symbol)) != 0) { + return GRPC_COMPRESS_LEVEL_NONE; + } else if (RTEST(rb_funcall(level_name, rb_intern("=="), 1, low_symbol)) != + 0) { + return GRPC_COMPRESS_LEVEL_LOW; + } else if (RTEST(rb_funcall(level_name, rb_intern("=="), 1, medium_symbol)) != + 0) { + return GRPC_COMPRESS_LEVEL_MED; + } else if (RTEST(rb_funcall(level_name, rb_intern("=="), 1, high_symbol)) != + 0) { + return GRPC_COMPRESS_LEVEL_HIGH; + } else { + rb_raise(rb_eArgError, + "Unrecognized compression level name." + "Valid compression level names are none, low, medium, and high."); + } } -void grpc_rb_compression_options_set_default_level_helper( - grpc_compression_options *compression_options, - grpc_compression_level level) { - compression_options->default_level.is_set |= 1; - compression_options->default_level.level = level; +/* Wrapper over grpc_rb_compression_options_level_name_to_value available for + * use or testing. + * Raises an exception for unrecognized level names. */ +VALUE grpc_rb_compression_options_level_name_to_value(VALUE self, + VALUE level_name) { + return INT2NUM((int)grpc_rb_compression_options_level_name_to_value_internal( + level_name)); } /* Sets the default compression level, given the name of a compression level. * Throws an error if no algorithm matched. */ -VALUE grpc_rb_compression_options_set_default_level(VALUE self, - VALUE new_level) { - char *level_name = NULL; - grpc_rb_compression_options *wrapper = NULL; - long name_len = 0; - VALUE ruby_str = Qnil; - - TypedData_Get_Struct(self, grpc_rb_compression_options, - &grpc_rb_compression_options_data_type, wrapper); - - /* Take both string and symbol parameters */ - ruby_str = rb_funcall(new_level, rb_intern("to_s"), 0); - - level_name = RSTRING_PTR(ruby_str); - name_len = RSTRING_LEN(ruby_str); - - /* Check the compression level of the name passed in, and see which macro - * from the GRPC core header files match. */ - if (strncmp(level_name, "none", name_len) == 0) { - grpc_rb_compression_options_set_default_level_helper( - wrapper->wrapped, GRPC_COMPRESS_LEVEL_NONE); - } else if (strncmp(level_name, "low", name_len) == 0) { - grpc_rb_compression_options_set_default_level_helper( - wrapper->wrapped, GRPC_COMPRESS_LEVEL_LOW); - } else if (strncmp(level_name, "medium", name_len) == 0) { - grpc_rb_compression_options_set_default_level_helper( - wrapper->wrapped, GRPC_COMPRESS_LEVEL_MED); - } else if (strncmp(level_name, "high", name_len) == 0) { - grpc_rb_compression_options_set_default_level_helper( - wrapper->wrapped, GRPC_COMPRESS_LEVEL_HIGH); - } else { - rb_raise(rb_eNameError, - "Invalid compression level name. Supported levels: none, low, " - "medium, high"); - } - - return Qnil; +void grpc_rb_compression_options_set_default_level( + grpc_compression_options *options, VALUE new_level_name) { + options->default_level.level = + grpc_rb_compression_options_level_name_to_value_internal(new_level_name); + options->default_level.is_set = 1; } /* Gets the internal value of a compression algorithm suitable as the value * in a GRPC core channel arguments hash. + * algorithm_value is an out parameter. * Raises an error if the name of the algorithm passed in is invalid. */ -void grpc_rb_compression_options_get_internal_value_of_algorithm( - VALUE algorithm_name, grpc_compression_algorithm *compression_algorithm) { - VALUE ruby_str = Qnil; +void grpc_rb_compression_options_algorithm_name_to_value_internal( + grpc_compression_algorithm *algorithm_value, VALUE algorithm_name) { char *name_str = NULL; long name_len = 0; + VALUE algorithm_name_as_string = Qnil; - /* Accept ruby symbol and string parameters. */ - ruby_str = rb_funcall(algorithm_name, rb_intern("to_s"), 0); - name_str = RSTRING_PTR(ruby_str); - name_len = RSTRING_LEN(ruby_str); + Check_Type(algorithm_name, T_SYMBOL); + + /* Convert the algorithm symbol to a ruby string, so that we can get the + * correct C string out of it. */ + algorithm_name_as_string = rb_funcall(algorithm_name, rb_intern("to_s"), 0); + + name_str = RSTRING_PTR(algorithm_name_as_string); + name_len = RSTRING_LEN(algorithm_name_as_string); /* Raise an error if the name isn't recognized as a compression algorithm by * the algorithm parse function * in GRPC core. */ - if (!grpc_compression_algorithm_parse(name_str, name_len, - compression_algorithm)) { - rb_raise(rb_eNameError, - "Invalid compression algorithm name."); + if (!grpc_compression_algorithm_parse(name_str, name_len, algorithm_value)) { + rb_raise(rb_eNameError, "Invalid compression algorithm name: %s", + StringValueCStr(algorithm_name_as_string)); } } +/* Wrapper around algorithm_name_to_value_internal function available for use or + * testing. */ +VALUE grpc_rb_compression_options_algorithm_name_to_value( + VALUE self, VALUE algorithm_name) { + grpc_compression_algorithm algorithm_value; + grpc_rb_compression_options_algorithm_name_to_value_internal(&algorithm_value, + algorithm_name); + + return INT2NUM((int)algorithm_value); +} + +/* Indicates whether a given algorithm is enabled on this instance, given the + * readable algorithm name. */ +VALUE grpc_rb_compression_options_is_algorithm_enabled(VALUE self, + VALUE algorithm_name) { + grpc_rb_compression_options *wrapper = NULL; + grpc_compression_algorithm internal_algorithm_value; + + TypedData_Get_Struct(self, grpc_rb_compression_options, + &grpc_rb_compression_options_data_type, wrapper); + grpc_rb_compression_options_algorithm_name_to_value_internal( + &internal_algorithm_value, algorithm_name); + + if (grpc_compression_options_is_algorithm_enabled(wrapper->wrapped, + internal_algorithm_value)) { + return Qtrue; + } + return Qfalse; +} + /* Sets the default algorithm to the name of the algorithm passed in. * Raises an error if the name is not a valid compression algorithm name. */ -VALUE grpc_rb_compression_options_set_default_algorithm(VALUE self, - VALUE algorithm_name) { +void grpc_rb_compression_options_set_default_algorithm( + grpc_compression_options *options, VALUE algorithm_name) { + grpc_rb_compression_options_algorithm_name_to_value_internal( + &options->default_algorithm.algorithm, algorithm_name); + options->default_algorithm.is_set = 1; +} + +/* Disables an algorithm on the current instance, given the name of an + * algorithm. + * Fails if the algorithm name is invalid. */ +void grpc_rb_compression_options_disable_algorithm( + grpc_compression_options *compression_options, VALUE algorithm_name) { + grpc_compression_algorithm internal_algorithm_value; + + grpc_rb_compression_options_algorithm_name_to_value_internal( + &internal_algorithm_value, algorithm_name); + grpc_compression_options_disable_algorithm(compression_options, + internal_algorithm_value); +} + +/* Provides a ruby hash of GRPC core channel argument key-values that + * correspond to the compression settings on this instance. */ +VALUE grpc_rb_compression_options_to_hash(VALUE self) { grpc_rb_compression_options *wrapper = NULL; + grpc_compression_options *compression_options = NULL; + VALUE channel_arg_hash = rb_hash_new(); + VALUE key = Qnil; + VALUE value = Qnil; TypedData_Get_Struct(self, grpc_rb_compression_options, &grpc_rb_compression_options_data_type, wrapper); + compression_options = wrapper->wrapped; - grpc_rb_compression_options_get_internal_value_of_algorithm( - algorithm_name, &wrapper->wrapped->default_algorithm.algorithm); - wrapper->wrapped->default_algorithm.is_set |= 1; + /* Add key-value pairs to the new Ruby hash. It can be used + * as GRPC core channel arguments. */ + if (compression_options->default_level.is_set) { + key = rb_str_new2(GRPC_COMPRESSION_CHANNEL_DEFAULT_LEVEL); + value = INT2NUM((int)compression_options->default_level.level); + rb_hash_aset(channel_arg_hash, key, value); + } - return Qnil; + if (compression_options->default_algorithm.is_set) { + key = rb_str_new2(GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM); + value = INT2NUM((int)compression_options->default_algorithm.algorithm); + rb_hash_aset(channel_arg_hash, key, value); + } + + key = rb_str_new2(GRPC_COMPRESSION_CHANNEL_ENABLED_ALGORITHMS_BITSET); + value = INT2NUM((int)compression_options->enabled_algorithms_bitset); + rb_hash_aset(channel_arg_hash, key, value); + + return channel_arg_hash; +} + +/* Converts an internal enum level value to a readable level name. + * Fails if the level value is invalid. */ +VALUE grpc_rb_compression_options_level_value_to_name_internal( + grpc_compression_level compression_value) { + switch (compression_value) { + case GRPC_COMPRESS_LEVEL_NONE: + return rb_const_get(grpc_rb_cCompressionOptions, + rb_intern("COMPRESS_NONE_SYM")); + case GRPC_COMPRESS_LEVEL_LOW: + return rb_const_get(grpc_rb_cCompressionOptions, + rb_intern("COMPRESS_LOW_SYM")); + case GRPC_COMPRESS_LEVEL_MED: + return rb_const_get(grpc_rb_cCompressionOptions, + rb_intern("COMPRESS_MEDIUM_SYM")); + case GRPC_COMPRESS_LEVEL_HIGH: + return rb_const_get(grpc_rb_cCompressionOptions, + rb_intern("COMPRESS_HIGH_SYM")); + default: + rb_raise( + rb_eArgError, + "Failed to convert compression level value to name for value: %d", + (int)compression_value); + } +} + +/* Wrapper of internal method that makes it available for use and testing. */ +VALUE grpc_rb_compression_options_level_value_to_name(VALUE self, + VALUE compression_value) { + Check_Type(compression_value, T_FIXNUM); + return grpc_rb_compression_options_level_value_to_name_internal( + (grpc_compression_level)NUM2INT(compression_value)); +} + +/* Converts an algorithm internal enum value to a readable name. + * Fails if the enum value is invalid. */ +VALUE grpc_rb_compression_options_algorithm_value_to_name_internal( + grpc_compression_algorithm internal_value) { + char *algorithm_name = NULL; + + if (!grpc_compression_algorithm_name(internal_value, &algorithm_name)) { + rb_raise(rb_eArgError, "Failed to convert algorithm value to name"); + } + + return ID2SYM(rb_intern(algorithm_name)); +} + +/* Wrapper of algorithm_to_name internal function available for ues and testing. + */ +VALUE grpc_rb_compression_options_algorithm_value_to_name( + VALUE self, VALUE algorithm_value) { + grpc_compression_algorithm algorithm_internal_value; + algorithm_internal_value = + (grpc_compression_algorithm)NUM2INT(algorithm_value); + + return grpc_rb_compression_options_algorithm_value_to_name_internal( + algorithm_internal_value); } /* Gets the internal value of the default compression level that is to be passed - * to the - * the GRPC core as a channel argument value. + * to the GRPC core as a channel argument value. * A nil return value means that it hasn't been set. */ -VALUE grpc_rb_compression_options_default_algorithm_internal_value(VALUE self) { +VALUE grpc_rb_compression_options_get_default_algorithm_internal_value( + VALUE self) { grpc_rb_compression_options *wrapper = NULL; TypedData_Get_Struct(self, grpc_rb_compression_options, @@ -228,15 +366,27 @@ VALUE grpc_rb_compression_options_default_algorithm_internal_value(VALUE self) { if (wrapper->wrapped->default_algorithm.is_set) { return INT2NUM(wrapper->wrapped->default_algorithm.algorithm); - } else { - return Qnil; } + return Qnil; } -/* Gets the internal value of the default compression level that is to be passed - * to the GRPC core as a channel argument value. - * A nil return value means that it hasn't been set. */ -VALUE grpc_rb_compression_options_default_level_internal_value(VALUE self) { +/* Gets the readable name of the default algorithm if one has been set. + * Returns nil if no algorithm has been set. */ +VALUE grpc_rb_compression_options_get_default_algorithm(VALUE self) { + VALUE algorithm_value = + grpc_rb_compression_options_get_default_algorithm_internal_value(self); + + if (RTEST(algorithm_value)) { + return grpc_rb_compression_options_algorithm_value_to_name(self, + algorithm_value); + } + + return Qnil; +} + +/* Gets the internal enum value of the default algorithm if one has been set. + * Returns nil if no default algorithm has been set. */ +VALUE grpc_rb_compression_options_get_default_level_internal_value(VALUE self) { grpc_rb_compression_options *wrapper = NULL; TypedData_Get_Struct(self, grpc_rb_compression_options, @@ -244,64 +394,121 @@ VALUE grpc_rb_compression_options_default_level_internal_value(VALUE self) { if (wrapper->wrapped->default_level.is_set) { return INT2NUM((int)wrapper->wrapped->default_level.level); - } else { - return Qnil; } + return Qnil; } -/* Disables compression algorithms by their names. Raises an error if an unkown - * name was passed. */ -VALUE grpc_rb_compression_options_disable_algorithms(int argc, VALUE *argv, - VALUE self) { - VALUE algorithm_names = Qnil; - VALUE ruby_str = Qnil; - grpc_compression_algorithm internal_algorithm_value; +/* Gets the internal value of the default compression level that is to be passed + * to the GRPC core as a channel argument value. + * A nil return value means that it hasn't been set. */ +VALUE grpc_rb_compression_options_get_default_level(VALUE self) { + grpc_compression_level internal_value; + VALUE ruby_value = + grpc_rb_compression_options_get_default_level_internal_value(self); + + if (RTEST(ruby_value)) { + internal_value = (grpc_compression_level)NUM2INT(ruby_value); + return grpc_rb_compression_options_level_value_to_name_internal( + internal_value); + } + + return Qnil; +} - /* read variadic argument list of names into the algorithm_name ruby array. */ - rb_scan_args(argc, argv, "0*", &algorithm_names); +/* Gets a list of the disabled algorithms as readable names. + * Returns an empty list of no algorithms have been disabled. */ +VALUE grpc_rb_compression_options_get_disabled_algorithms(VALUE self) { + VALUE disabled_algorithms = rb_ary_new(); + grpc_compression_algorithm internal_value; + grpc_rb_compression_options *wrapper = NULL; + + TypedData_Get_Struct(self, grpc_rb_compression_options, + &grpc_rb_compression_options_data_type, wrapper); - for (int i = 0; i < RARRAY_LEN(algorithm_names); i++) { - ruby_str = - rb_funcall(rb_ary_entry(algorithm_names, i), rb_intern("to_s"), 0); - grpc_rb_compression_options_get_internal_value_of_algorithm( - ruby_str, &internal_algorithm_value); - rb_funcall(self, rb_intern("disable_algorithm_internal"), 1, - LONG2NUM((long)internal_algorithm_value)); + for (internal_value = GRPC_COMPRESS_NONE; + internal_value < GRPC_COMPRESS_ALGORITHMS_COUNT; internal_value++) { + if (!grpc_compression_options_is_algorithm_enabled(wrapper->wrapped, + internal_value)) { + rb_ary_push(disabled_algorithms, + grpc_rb_compression_options_algorithm_value_to_name_internal( + internal_value)); + } } + return disabled_algorithms; +} - return Qnil; +/* Provides a bitset as a ruby number that is suitable to pass to + * the GRPC core as a channel argument to enable compression algorithms. */ +VALUE grpc_rb_compression_options_get_enabled_algorithms_bitset(VALUE self) { + grpc_rb_compression_options *wrapper = NULL; + + TypedData_Get_Struct(self, grpc_rb_compression_options, + &grpc_rb_compression_options_data_type, wrapper); + return INT2NUM((int)wrapper->wrapped->enabled_algorithms_bitset); } -/* Provides a ruby hash of GRPC core channel argument key-values that - * correspond to the compression settings on this instance. */ -VALUE grpc_rb_compression_options_to_hash(VALUE self) { +/* Initializes the compression options wrapper. + * Takes an optional hash parameter. + * + * Example call-seq: + * options = CompressionOptions.new( + * default_level: :none, + * disabled_algorithms: [:gzip] + * ) + * channel_arg hash = Hash.new[...] + * channel_arg_hash_with_compression_options = channel_arg_hash.merge(options) + */ +VALUE grpc_rb_compression_options_init(int argc, VALUE *argv, VALUE self) { grpc_rb_compression_options *wrapper = NULL; - grpc_compression_options *compression_options = NULL; - VALUE channel_arg_hash = rb_funcall(rb_cHash, rb_intern("new"), 0); + VALUE default_algorithm = Qnil; + VALUE default_level = Qnil; + VALUE disabled_algorithms = Qnil; + VALUE algorithm_name = Qnil; + VALUE hash_arg = Qnil; + + rb_scan_args(argc, argv, "01", &hash_arg); + + /* Check if the hash parameter was passed, or if invalid arguments were + * passed. */ + if (hash_arg == Qnil) { + return self; + } else if (TYPE(hash_arg) != T_HASH || argc > 1) { + rb_raise(rb_eArgError, + "Invalid arguments. Expecting optional hash parameter"); + } TypedData_Get_Struct(self, grpc_rb_compression_options, &grpc_rb_compression_options_data_type, wrapper); - compression_options = wrapper->wrapped; - /* Add key-value pairs to the new Ruby hash. It can be used - * as GRPC core channel arguments. */ - if (compression_options->default_level.is_set) { - rb_funcall(channel_arg_hash, rb_intern("[]="), 2, - rb_str_new2(GRPC_COMPRESSION_CHANNEL_DEFAULT_LEVEL), - INT2NUM((int)compression_options->default_level.level)); + /* Set the default algorithm if one was chosen. */ + default_algorithm = + rb_hash_aref(hash_arg, ID2SYM(rb_intern("default_algorithm"))); + if (default_algorithm != Qnil) { + grpc_rb_compression_options_set_default_algorithm(wrapper->wrapped, + default_algorithm); } - if (compression_options->default_algorithm.is_set) { - rb_funcall(channel_arg_hash, rb_intern("[]="), 2, - rb_str_new2(GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM), - INT2NUM((int)compression_options->default_algorithm.algorithm)); + /* Set the default level if one was chosen. */ + default_level = rb_hash_aref(hash_arg, ID2SYM(rb_intern("default_level"))); + if (default_level != Qnil) { + grpc_rb_compression_options_set_default_level(wrapper->wrapped, + default_level); } - rb_funcall(channel_arg_hash, rb_intern("[]="), 2, - rb_str_new2(GRPC_COMPRESSION_CHANNEL_ENABLED_ALGORITHMS_BITSET), - INT2NUM((int)compression_options->enabled_algorithms_bitset)); + /* Set the disabled algorithms if any were chosen. */ + disabled_algorithms = + rb_hash_aref(hash_arg, ID2SYM(rb_intern("disabled_algorithms"))); + if (disabled_algorithms != Qnil) { + Check_Type(disabled_algorithms, T_ARRAY); + + for (int i = 0; i < RARRAY_LEN(disabled_algorithms); i++) { + algorithm_name = rb_ary_entry(disabled_algorithms, i); + grpc_rb_compression_options_disable_algorithm(wrapper->wrapped, + algorithm_name); + } + } - return channel_arg_hash; + return self; } void Init_grpc_compression_options() { @@ -312,40 +519,71 @@ void Init_grpc_compression_options() { rb_define_alloc_func(grpc_rb_cCompressionOptions, grpc_rb_compression_options_alloc); - /* Private method for disabling algorithms by a variadic list of names. */ - rb_define_private_method(grpc_rb_cCompressionOptions, "disable_algorithms", - grpc_rb_compression_options_disable_algorithms, -1); - /* Private method for disabling an algorithm by its enum value. */ - rb_define_private_method( - grpc_rb_cCompressionOptions, "disable_algorithm_internal", - grpc_rb_compression_options_disable_compression_algorithm_internal, 1); - - /* Private method for getting the bitset of enabled algorithms. */ - rb_define_private_method( - grpc_rb_cCompressionOptions, "enabled_algorithms_bitset", - grpc_rb_compression_options_get_enabled_algorithms_bitset, 0); - - /* Private method for setting the default algorithm by name. */ - rb_define_private_method(grpc_rb_cCompressionOptions, "set_default_algorithm", - grpc_rb_compression_options_set_default_algorithm, - 1); - /* Private method for getting the internal enum value of the default - * algorithm. */ - rb_define_private_method( + /* Initializes the ruby wrapper. #new method takes an optional hash argument. + */ + rb_define_method(grpc_rb_cCompressionOptions, "initialize", + grpc_rb_compression_options_init, -1); + + /* Gets the bitset of enabled algorithms. */ + rb_define_method(grpc_rb_cCompressionOptions, "enabled_algorithms_bitset", + grpc_rb_compression_options_get_enabled_algorithms_bitset, + 0); + + /* Methods for getting the default algorithm, default level, and disabled + * algorithms as readable names. */ + rb_define_method(grpc_rb_cCompressionOptions, "default_algorithm", + grpc_rb_compression_options_get_default_algorithm, 0); + rb_define_method(grpc_rb_cCompressionOptions, "default_level", + grpc_rb_compression_options_get_default_level, 0); + rb_define_method(grpc_rb_cCompressionOptions, "disabled_algorithms", + grpc_rb_compression_options_get_disabled_algorithms, 0); + + /* Methods for getting the internal enum default algorithm and level enum + * values of an instance. */ + rb_define_method( grpc_rb_cCompressionOptions, "default_algorithm_internal_value", - grpc_rb_compression_options_default_algorithm_internal_value, 0); - - /* Private method for setting the default compression level by name. */ - rb_define_private_method(grpc_rb_cCompressionOptions, "set_default_level", - grpc_rb_compression_options_set_default_level, 1); - - /* Private method for getting the internal enum value of the default level. */ - rb_define_private_method( - grpc_rb_cCompressionOptions, "default_level_internal_value", - grpc_rb_compression_options_default_level_internal_value, 0); - - /* Public method for returning a hash of the compression settings suitable + grpc_rb_compression_options_get_default_algorithm_internal_value, 0); + rb_define_method(grpc_rb_cCompressionOptions, "default_level_internal_value", + grpc_rb_compression_options_get_default_level_internal_value, + 0); + + /* Determines whether or not an algorithm is enabled, given a readable + * algorithm name.*/ + rb_define_method(grpc_rb_cCompressionOptions, "is_algorithm_enabled", + grpc_rb_compression_options_is_algorithm_enabled, 1); + + /* Methods for converting to and from algorithm enum values and their readable + * names. */ + rb_define_singleton_method( + grpc_rb_cCompressionOptions, "algorithm_name_to_value", + grpc_rb_compression_options_algorithm_name_to_value, 1); + rb_define_singleton_method( + grpc_rb_cCompressionOptions, "algorithm_value_to_name", + grpc_rb_compression_options_algorithm_value_to_name, 1); + + /* Methods for converting to and from compression level enum values and their + * readable names. */ + rb_define_singleton_method(grpc_rb_cCompressionOptions, "level_name_to_value", + grpc_rb_compression_options_level_name_to_value, + 1); + rb_define_singleton_method(grpc_rb_cCompressionOptions, "level_value_to_name", + grpc_rb_compression_options_level_value_to_name, + 1); + + /* Provides a hash of the compression settings suitable * for passing to server or channel args. */ rb_define_method(grpc_rb_cCompressionOptions, "to_hash", grpc_rb_compression_options_to_hash, 0); + rb_define_alias(grpc_rb_cCompressionOptions, "to_channel_arg_hash", + "to_hash"); + + /* Ruby symbols for the names of the different compression levels. */ + rb_define_const(grpc_rb_cCompressionOptions, "COMPRESS_NONE_SYM", + ID2SYM(rb_intern("none"))); + rb_define_const(grpc_rb_cCompressionOptions, "COMPRESS_LOW_SYM", + ID2SYM(rb_intern("low"))); + rb_define_const(grpc_rb_cCompressionOptions, "COMPRESS_MEDIUM_SYM", + ID2SYM(rb_intern("medium"))); + rb_define_const(grpc_rb_cCompressionOptions, "COMPRESS_HIGH_SYM", + ID2SYM(rb_intern("high"))); } diff --git a/src/ruby/lib/grpc.rb b/src/ruby/lib/grpc.rb index 2dae3a64d61..79fa705b1c9 100644 --- a/src/ruby/lib/grpc.rb +++ b/src/ruby/lib/grpc.rb @@ -35,7 +35,6 @@ require_relative 'grpc/logconfig' require_relative 'grpc/notifier' require_relative 'grpc/version' require_relative 'grpc/core/time_consts' -require_relative 'grpc/core/compression_options' require_relative 'grpc/generic/active_call' require_relative 'grpc/generic/client_stub' require_relative 'grpc/generic/service' diff --git a/src/ruby/lib/grpc/core/compression_options.rb b/src/ruby/lib/grpc/core/compression_options.rb deleted file mode 100644 index 757c69f8e05..00000000000 --- a/src/ruby/lib/grpc/core/compression_options.rb +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright 2015, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -require_relative '../grpc' - -# GRPC contains the General RPC module. -module GRPC - module Core - # Wrapper for grpc_compression_options in core - # This class is defined as a C extension but is reopened here - # to add the initialization logic. - # - # This class wraps a GRPC core compression options. - # - # It can be used to create a channel argument key-value hash - # with keys and values that correspond to the compression settings - # provided here. - # - # call-seq: - # options = CompressionOptions.new( - # default_level: low, - # disabled_algorithms: [:]) - # - # channel_args = Hash.new[...] - # channel_args_with_compression_args = channel_args.merge(options) - class CompressionOptions - alias_method :to_channel_arg_hash, :to_hash - - # Initializes a CompresionOptions instance. - # Starts out with all available compression - # algorithms enabled by default. - # - # Valid algorithms are those supported by the GRPC core - # - # @param default_level [String | Symbol] - # one of 'none', 'low', 'medium', 'high' - # @param default_algorithm [String | Symbol] - # a valid GRPC algorithm - # @param disabled_algorithms [Array] - # can contain valid GRPC algorithm names - def initialize(default_algorithm: nil, - default_level: nil, - disabled_algorithms: []) - # Convert possible symbols to strings for comparisons - disabled_algorithms = disabled_algorithms.map(&:to_s) - - if disabled_algorithms.include?(default_algorithm.to_s) - fail ArgumentError("#{default_algorithm} is in disabled_algorithms") - end - - set_default_algorithm(default_algorithm.to_s) unless - default_algorithm.nil? - - set_default_level(default_level.to_s) unless - default_level.nil? - - # *disabled_algorithms spreads array into variadic method parameters - disable_algorithms(*disabled_algorithms) unless - disabled_algorithms.nil? || disabled_algorithms.empty? - end - - def to_s - to_hash.to_s - end - end - end -end diff --git a/src/ruby/spec/compression_options_spec.rb b/src/ruby/spec/compression_options_spec.rb index 32c7a1bc943..0b057018698 100644 --- a/src/ruby/spec/compression_options_spec.rb +++ b/src/ruby/spec/compression_options_spec.rb @@ -59,6 +59,13 @@ describe GRPC::Core::CompressionOptions do high: 3 } + it 'compression level name constants should match expections' do + expect(GRPC::Core::CompressionOptions::COMPRESS_NONE_SYM).to eq(:none) + expect(GRPC::Core::CompressionOptions::COMPRESS_LOW_SYM).to eq(:low) + expect(GRPC::Core::CompressionOptions::COMPRESS_MEDIUM_SYM).to eq(:medium) + expect(GRPC::Core::CompressionOptions::COMPRESS_HIGH_SYM).to eq(:high) + end + it 'implements to_s' do expect { GRPC::Core::CompressionOptions.new.to_s }.to_not raise_error end @@ -79,23 +86,25 @@ describe GRPC::Core::CompressionOptions do it 'gives the correct channel args after everything has been disabled' do options = GRPC::Core::CompressionOptions.new( - default_algorithm: 'identity', - default_level: 'none', - disabled_algorithms: [:gzip, :deflate] + default_algorithm: :identity, + default_level: :none, + disabled_algorithms: ALGORITHMS.keys ) - expect(options.to_hash).to( - eql('grpc.default_compression_algorithm' => 0, - 'grpc.default_compression_level' => 0, - 'grpc.compression_enabled_algorithms_bitset' => 0x1) - ) + channel_arg_hash = options.to_hash + expect(channel_arg_hash['grpc.default_compression_algorithm']).to eq(0) + expect(channel_arg_hash['grpc.default_compression_level']).to eq(0) + + # Don't care if the "identity" algorithm bit is set or unset + bitset = channel_arg_hash['grpc.compression_enabled_algorithms_bitset'] + expect(bitset & ~ALGORITHM_BITS[:identity]).to eq(0) end it 'gives correct channel args with all args set' do options = GRPC::Core::CompressionOptions.new( - default_algorithm: 'gzip', - default_level: 'low', - disabled_algorithms: ['deflate'] + default_algorithm: :gzip, + default_level: :low, + disabled_algorithms: [:deflate] ) expected_bitset = ALL_ENABLED_BITSET & ~ALGORITHM_BITS[:deflate] @@ -109,8 +118,8 @@ describe GRPC::Core::CompressionOptions do it 'gives correct channel args when no algorithms are disabled' do options = GRPC::Core::CompressionOptions.new( - default_algorithm: 'identity', - default_level: 'high' + default_algorithm: :identity, + default_level: :high ) expect(options.to_hash).to( @@ -119,136 +128,181 @@ describe GRPC::Core::CompressionOptions do 'grpc.compression_enabled_algorithms_bitset' => ALL_ENABLED_BITSET) ) end + end - # Raising an error in when attempting to set the default algorithm - # to something that is also requested to be disabled - it 'gives raises an error when the chosen default algorithm is disabled' do - blk = proc do - GRPC::Core::CompressionOptions.new( - default_algorithm: :gzip, - disabled_algorithms: [:gzip]) - end + describe '#new with bad parameters' do + it 'should fail with more than one parameter' do + blk = proc { GRPC::Core::CompressionOptions.new(:gzip, :none) } expect { blk.call }.to raise_error end - end - # Test the private methods in the C extension that interact with - # the wrapped grpc_compression_options. - # - # Using #send to call private methods. - describe 'private internal methods' do - it 'mutating functions and accessors should be private' do - options = GRPC::Core::CompressionOptions.new + it 'should fail with a non-hash parameter' do + blk = proc { GRPC::Core::CompressionOptions.new(:gzip) } + expect { blk.call }.to raise_error + end + end - [:disable_algorithm_internal, - :disable_algorithms, - :set_default_algorithm, - :set_default_level, - :default_algorithm_internal_value, - :default_level_internal_value].each do |method_name| - expect(options.private_methods).to include(method_name) + describe '#level_name_to_value' do + it 'should give the correct internal values from compression level names' do + cls = GRPC::Core::CompressionOptions + COMPRESS_LEVELS.each_pair do |name, internal_value| + expect(cls.level_name_to_value(name)).to eq(internal_value) end end - describe '#disable_algorithms' do - ALGORITHMS.each_pair do |name, internal_value| - it "passes #{internal_value} to internal method for #{name}" do - options = GRPC::Core::CompressionOptions.new - expect(options).to receive(:disable_algorithm_internal) - .with(internal_value) - - options.send(:disable_algorithms, name) + [:gzip, :deflate, :any, Object.new, 'none', 'low', 1].each do |name| + it "should fail for parameter #{name} of class #{name.class}" do + blk = proc do + GRPC::Core::CompressionOptions.level_name_to_value(name) end + expect { blk.call }.to raise_error end + end + end - it 'should work with multiple parameters' do - options = GRPC::Core::CompressionOptions.new + describe '#level_value_to_name' do + it 'should give the correct internal values from compression level names' do + cls = GRPC::Core::CompressionOptions + COMPRESS_LEVELS.each_pair do |name, internal_value| + expect(cls.level_value_to_name(internal_value)).to eq(name) + end + end - ALGORITHMS.values do |internal_value| - expect(options).to receive(:disable_algorithm_internal) - .with(internal_value) + [:gzip, :any, Object.new, '1', :low].each do |name| + it "should fail for parameter #{name} of class #{name.class}" do + blk = proc do + GRPC::Core::CompressionOptions.level_value_to_name(name) end + expect { blk.call }.to raise_error + end + end + end - # disabled_algorithms is a private, variadic method - options.send(:disable_algorithms, *ALGORITHMS.keys) + describe '#algorithm_name_to_value' do + it 'should give the correct internal values from algorithm names' do + cls = GRPC::Core::CompressionOptions + ALGORITHMS.each_pair do |name, internal_value| + expect(cls.algorithm_name_to_value(name)).to eq(internal_value) end end - describe '#new default values' do - it 'should start out with all algorithms enabled' do - options = GRPC::Core::CompressionOptions.new - bitset = options.send(:enabled_algorithms_bitset) - expect(bitset).to eql(ALL_ENABLED_BITSET) + ['gzip', 'deflate', :any, Object.new, :none, :low, 1].each do |name| + it "should fail for parameter #{name} of class #{name.class}" do + blk = proc do + GRPC::Core::CompressionOptions.algorithm_name_to_value(name) + end + expect { blk.call }.to raise_error end + end + end - it 'should start out with no default algorithm' do - options = GRPC::Core::CompressionOptions.new - expect(options.send(:default_algorithm_internal_value)).to be_nil + describe '#algorithm_value_to_name' do + it 'should give the correct internal values from algorithm names' do + cls = GRPC::Core::CompressionOptions + ALGORITHMS.each_pair do |name, internal_value| + expect(cls.algorithm_value_to_name(internal_value)).to eq(name) end + end - it 'should start out with no default level' do - options = GRPC::Core::CompressionOptions.new - expect(options.send(:default_level_internal_value)).to be_nil + ['gzip', :deflate, :any, Object.new, :low, '1'].each do |value| + it "should fail for parameter #{value} of class #{value.class}" do + blk = proc do + GRPC::Core::CompressionOptions.algorithm_value_to_name(value) + end + expect { blk.call }.to raise_error end end + end - describe '#enabled_algoritms_bitset' do - it 'should respond to disabling one algorithm' do - options = GRPC::Core::CompressionOptions.new - options.send(:disable_algorithms, :gzip) - current_bitset = options.send(:enabled_algorithms_bitset) - expect(current_bitset & ALGORITHM_BITS[:gzip]).to be_zero + describe '#default_algorithm and #default_algorithm_internal_value' do + it 'can set the default algorithm and then read it back out' do + ALGORITHMS.each_pair do |name, internal_value| + options = GRPC::Core::CompressionOptions.new(default_algorithm: name) + expect(options.default_algorithm).to eq(name) + expect(options.default_algorithm_internal_value).to eq(internal_value) end + end - it 'should respond to disabling multiple algorithms' do - options = GRPC::Core::CompressionOptions.new + it 'returns nil if unset' do + options = GRPC::Core::CompressionOptions.new + expect(options.default_algorithm).to be_nil + expect(options.default_algorithm_internal_value).to be_nil + end + end - # splitting up algorithms array since #disable_algorithms is variadic - options.send(:disable_algorithms, *ALGORITHMS.keys) - current_bitset = options.send(:enabled_algorithms_bitset) - expect(current_bitset).to eql(ALL_DISABLED_BITSET) + describe '#default_level and #default_level_internal_value' do + it 'can set the default level and read it back out' do + COMPRESS_LEVELS.each_pair do |name, internal_value| + options = GRPC::Core::CompressionOptions.new(default_level: name) + expect(options.default_level).to eq(name) + expect(options.default_level_internal_value).to eq(internal_value) end end - describe 'setting the default algorithm by name' do - it 'should set the internal value of the default algorithm' do - ALGORITHMS.each_pair do |name, expected_internal_value| - options = GRPC::Core::CompressionOptions.new - options.send(:set_default_algorithm, name) - internal_value = options.send(:default_algorithm_internal_value) - expect(internal_value).to eql(expected_internal_value) - end - end + it 'returns nil if unset' do + options = GRPC::Core::CompressionOptions.new + expect(options.default_level).to be_nil + expect(options.default_level_internal_value).to be_nil + end + end - it 'should fail with invalid algorithm names' do - [:none, :low, :huffman, :unkown, Object.new, 1].each do |name| - blk = proc do - options = GRPC::CoreCompressionOptions.new - options.send(:set_default_algorithm, name) - end - expect { blk.call }.to raise_error - end + describe '#disabled_algorithms' do + it 'can set the disabled algorithms and read them back out' do + options = GRPC::Core::CompressionOptions.new( + disabled_algorithms: [:gzip, :deflate]) + + [:gzip, :deflate].each do |name| + expect(options.disabled_algorithms.include?(name)).to eq(true) end + expect(options.disabled_algorithms.size).to eq(2) end - describe 'setting the default level by name' do - it 'should set the internal value of the default compression value' do - COMPRESS_LEVELS.each_pair do |level, expected_internal_value| - options = GRPC::Core::CompressionOptions.new - options.send(:set_default_level, level) - internal_value = options.send(:default_level_internal_value) - expect(internal_value).to eql(expected_internal_value) - end - end + it 'returns an empty list if no algorithms were disabled' do + options = GRPC::Core::CompressionOptions.new + expect(options.disabled_algorithms).to eq([]) + end + end - it 'should fail with invalid names' do - [:identity, :gzip, :unkown, :any, Object.new, 1].each do |name| - blk = proc do - GRPC::Core::CompressionOptions.new.send(:set_default_level, name) - end - expect { blk.call }.to raise_error + describe '#is_algorithm_enabled' do + it 'returns true if the algorithm is valid and not disabled' do + options = GRPC::Core::CompressionOptions.new(disabled_algorithms: [:gzip]) + expect(options.is_algorithm_enabled(:deflate)).to eq(true) + end + + it 'returns false if the algorithm is valid and disabled' do + options = GRPC::Core::CompressionOptions.new(disabled_algorithms: [:gzip]) + expect(options.is_algorithm_enabled(:gzip)).to eq(false) + end + + [:none, :any, 'gzip', Object.new, 1].each do |name| + it "should fail for parameter ${name} of class #{name.class}" do + options = GRPC::Core::CompressionOptions.new( + disabled_algorithms: [:gzip]) + + blk = proc do + options.is_algorithm_enabled(name) end + expect { blk.call }.to raise_error end end end + + describe '#enabled_algoritms_bitset' do + it 'should respond to not disabling any algorithms' do + options = GRPC::Core::CompressionOptions.new + expect(options.enabled_algorithms_bitset).to eq(ALL_ENABLED_BITSET) + end + + it 'should respond to disabling one algorithm' do + options = GRPC::Core::CompressionOptions.new( + disabled_algorithms: [:gzip]) + expect(options.enabled_algorithms_bitset & ALGORITHM_BITS[:gzip]).to eq(0) + end + + it 'should respond to disabling multiple algorithms' do + options = GRPC::Core::CompressionOptions.new( + disabled_algorithms: ALGORITHMS.keys) + expect(options.enabled_algorithms_bitset).to eql(ALL_DISABLED_BITSET) + end + end end From b72cc3dd871f4b0e72519c8bea3577c61e8abd83 Mon Sep 17 00:00:00 2001 From: Alex Polcyn Date: Sun, 10 Jul 2016 19:08:54 -0700 Subject: [PATCH 03/10] fixed unreferenced self C parameters --- src/ruby/ext/grpc/rb_compression_options.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ruby/ext/grpc/rb_compression_options.c b/src/ruby/ext/grpc/rb_compression_options.c index 2d3be4c7d07..51398262467 100644 --- a/src/ruby/ext/grpc/rb_compression_options.c +++ b/src/ruby/ext/grpc/rb_compression_options.c @@ -169,6 +169,7 @@ grpc_compression_level grpc_rb_compression_options_level_name_to_value_internal( * Raises an exception for unrecognized level names. */ VALUE grpc_rb_compression_options_level_name_to_value(VALUE self, VALUE level_name) { + (void)self; return INT2NUM((int)grpc_rb_compression_options_level_name_to_value_internal( level_name)); } @@ -215,6 +216,7 @@ void grpc_rb_compression_options_algorithm_name_to_value_internal( VALUE grpc_rb_compression_options_algorithm_name_to_value( VALUE self, VALUE algorithm_name) { grpc_compression_algorithm algorithm_value; + (void)self; grpc_rb_compression_options_algorithm_name_to_value_internal(&algorithm_value, algorithm_name); @@ -324,6 +326,7 @@ VALUE grpc_rb_compression_options_level_value_to_name_internal( /* Wrapper of internal method that makes it available for use and testing. */ VALUE grpc_rb_compression_options_level_value_to_name(VALUE self, VALUE compression_value) { + (void)self; Check_Type(compression_value, T_FIXNUM); return grpc_rb_compression_options_level_value_to_name_internal( (grpc_compression_level)NUM2INT(compression_value)); @@ -346,9 +349,9 @@ VALUE grpc_rb_compression_options_algorithm_value_to_name_internal( */ VALUE grpc_rb_compression_options_algorithm_value_to_name( VALUE self, VALUE algorithm_value) { - grpc_compression_algorithm algorithm_internal_value; - algorithm_internal_value = + grpc_compression_algorithm algorithm_internal_value = (grpc_compression_algorithm)NUM2INT(algorithm_value); + (void)self; return grpc_rb_compression_options_algorithm_value_to_name_internal( algorithm_internal_value); From 6c4709e275dce67aaaf249e24208bd3dc7f69a32 Mon Sep 17 00:00:00 2001 From: Alexander Polcyn Date: Mon, 11 Jul 2016 13:14:07 -0700 Subject: [PATCH 04/10] replaced constant compress level names with ids --- src/ruby/ext/grpc/rb_compression_options.c | 70 ++++++++-------------- src/ruby/spec/compression_options_spec.rb | 22 +++---- 2 files changed, 36 insertions(+), 56 deletions(-) diff --git a/src/ruby/ext/grpc/rb_compression_options.c b/src/ruby/ext/grpc/rb_compression_options.c index 51398262467..02bb3a5ddff 100644 --- a/src/ruby/ext/grpc/rb_compression_options.c +++ b/src/ruby/ext/grpc/rb_compression_options.c @@ -47,6 +47,12 @@ static VALUE grpc_rb_cCompressionOptions = Qnil; +/* Ruby Ids for the names of valid compression levels. */ +static VALUE id_compress_level_none = Qnil; +static VALUE id_compress_level_low = Qnil; +static VALUE id_compress_level_medium = Qnil; +static VALUE id_compress_level_high = Qnil; + /* grpc_rb_compression_options wraps a grpc_compression_options. * It can be used to get the channel argument key-values for specific * compression settings. */ @@ -121,47 +127,27 @@ VALUE grpc_rb_compression_options_disable_compression_algorithm_internal( return Qnil; } -/* Provides a bitset as a ruby number that is suitable to pass to - * the GRPC core as a channel argument to enable compression algorithms. */ /* Gets the compression internal enum value of a compression level given its * name. */ grpc_compression_level grpc_rb_compression_options_level_name_to_value_internal( VALUE level_name) { - VALUE none_symbol = Qnil; - VALUE low_symbol = Qnil; - VALUE medium_symbol = Qnil; - VALUE high_symbol = Qnil; - Check_Type(level_name, T_SYMBOL); - /* Ruby symbols that correspond to names of valid compression levels */ - none_symbol = - rb_const_get(grpc_rb_cCompressionOptions, rb_intern("COMPRESS_NONE_SYM")); - low_symbol = - rb_const_get(grpc_rb_cCompressionOptions, rb_intern("COMPRESS_LOW_SYM")); - medium_symbol = rb_const_get(grpc_rb_cCompressionOptions, - rb_intern("COMPRESS_MEDIUM_SYM")); - high_symbol = - rb_const_get(grpc_rb_cCompressionOptions, rb_intern("COMPRESS_HIGH_SYM")); - /* Check the compression level of the name passed in, and see which macro * from the GRPC core header files match. */ - if (RTEST(rb_funcall(level_name, rb_intern("=="), 1, none_symbol)) != 0) { + if (id_compress_level_none == SYM2ID(level_name)) { return GRPC_COMPRESS_LEVEL_NONE; - } else if (RTEST(rb_funcall(level_name, rb_intern("=="), 1, low_symbol)) != - 0) { + } else if (id_compress_level_low == SYM2ID(level_name)) { return GRPC_COMPRESS_LEVEL_LOW; - } else if (RTEST(rb_funcall(level_name, rb_intern("=="), 1, medium_symbol)) != - 0) { + } else if (id_compress_level_medium == SYM2ID(level_name)) { return GRPC_COMPRESS_LEVEL_MED; - } else if (RTEST(rb_funcall(level_name, rb_intern("=="), 1, high_symbol)) != - 0) { + } else if (id_compress_level_high == SYM2ID(level_name)) { return GRPC_COMPRESS_LEVEL_HIGH; - } else { - rb_raise(rb_eArgError, - "Unrecognized compression level name." - "Valid compression level names are none, low, medium, and high."); } + + rb_raise(rb_eArgError, + "Unrecognized compression level name." + "Valid compression level names are none, low, medium, and high."); } /* Wrapper over grpc_rb_compression_options_level_name_to_value available for @@ -170,6 +156,8 @@ grpc_compression_level grpc_rb_compression_options_level_name_to_value_internal( VALUE grpc_rb_compression_options_level_name_to_value(VALUE self, VALUE level_name) { (void)self; + Check_Type(level_name, T_SYMBOL); + return INT2NUM((int)grpc_rb_compression_options_level_name_to_value_internal( level_name)); } @@ -304,17 +292,13 @@ VALUE grpc_rb_compression_options_level_value_to_name_internal( grpc_compression_level compression_value) { switch (compression_value) { case GRPC_COMPRESS_LEVEL_NONE: - return rb_const_get(grpc_rb_cCompressionOptions, - rb_intern("COMPRESS_NONE_SYM")); + return ID2SYM(id_compress_level_none); case GRPC_COMPRESS_LEVEL_LOW: - return rb_const_get(grpc_rb_cCompressionOptions, - rb_intern("COMPRESS_LOW_SYM")); + return ID2SYM(id_compress_level_low); case GRPC_COMPRESS_LEVEL_MED: - return rb_const_get(grpc_rb_cCompressionOptions, - rb_intern("COMPRESS_MEDIUM_SYM")); + return ID2SYM(id_compress_level_medium); case GRPC_COMPRESS_LEVEL_HIGH: - return rb_const_get(grpc_rb_cCompressionOptions, - rb_intern("COMPRESS_HIGH_SYM")); + return ID2SYM(id_compress_level_high); default: rb_raise( rb_eArgError, @@ -580,13 +564,9 @@ void Init_grpc_compression_options() { rb_define_alias(grpc_rb_cCompressionOptions, "to_channel_arg_hash", "to_hash"); - /* Ruby symbols for the names of the different compression levels. */ - rb_define_const(grpc_rb_cCompressionOptions, "COMPRESS_NONE_SYM", - ID2SYM(rb_intern("none"))); - rb_define_const(grpc_rb_cCompressionOptions, "COMPRESS_LOW_SYM", - ID2SYM(rb_intern("low"))); - rb_define_const(grpc_rb_cCompressionOptions, "COMPRESS_MEDIUM_SYM", - ID2SYM(rb_intern("medium"))); - rb_define_const(grpc_rb_cCompressionOptions, "COMPRESS_HIGH_SYM", - ID2SYM(rb_intern("high"))); + /* Ruby ids for the names of the different compression levels. */ + id_compress_level_none = rb_intern("none"); + id_compress_level_low = rb_intern("low"); + id_compress_level_medium = rb_intern("medium"); + id_compress_level_high = rb_intern("high"); } diff --git a/src/ruby/spec/compression_options_spec.rb b/src/ruby/spec/compression_options_spec.rb index 0b057018698..8d617562043 100644 --- a/src/ruby/spec/compression_options_spec.rb +++ b/src/ruby/spec/compression_options_spec.rb @@ -59,13 +59,6 @@ describe GRPC::Core::CompressionOptions do high: 3 } - it 'compression level name constants should match expections' do - expect(GRPC::Core::CompressionOptions::COMPRESS_NONE_SYM).to eq(:none) - expect(GRPC::Core::CompressionOptions::COMPRESS_LOW_SYM).to eq(:low) - expect(GRPC::Core::CompressionOptions::COMPRESS_MEDIUM_SYM).to eq(:medium) - expect(GRPC::Core::CompressionOptions::COMPRESS_HIGH_SYM).to eq(:high) - end - it 'implements to_s' do expect { GRPC::Core::CompressionOptions.new.to_s }.to_not raise_error end @@ -143,10 +136,10 @@ describe GRPC::Core::CompressionOptions do end describe '#level_name_to_value' do - it 'should give the correct internal values from compression level names' do - cls = GRPC::Core::CompressionOptions - COMPRESS_LEVELS.each_pair do |name, internal_value| - expect(cls.level_name_to_value(name)).to eq(internal_value) + COMPRESS_LEVELS.each_pair do |name, internal_value| + it "should return value #{internal_value} for level #{name}" do + actual_value = GRPC::Core::CompressionOptions.level_name_to_value(name) + expect(actual_value).to eq(internal_value) end end @@ -161,6 +154,13 @@ describe GRPC::Core::CompressionOptions do end describe '#level_value_to_name' do + COMPRESS_LEVELS.each_pair do |name, internal_value| + it "should return level name #{name} for value #{internal_value}" do + actual_name = GRPC::Core::CompressionOptions.level_value_to_name( + internal_value) + expect(actual_name).to eq(name) + end + end it 'should give the correct internal values from compression level names' do cls = GRPC::Core::CompressionOptions COMPRESS_LEVELS.each_pair do |name, internal_value| From a0a0e1f7db012e6c5d3cf092c6a654805b78885c Mon Sep 17 00:00:00 2001 From: Alexander Polcyn Date: Mon, 11 Jul 2016 13:51:48 -0700 Subject: [PATCH 05/10] Removed expectations of entire provided algorithms list in tests --- src/ruby/spec/compression_options_spec.rb | 58 +++++++++++++---------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/src/ruby/spec/compression_options_spec.rb b/src/ruby/spec/compression_options_spec.rb index 8d617562043..7fa416ef095 100644 --- a/src/ruby/spec/compression_options_spec.rb +++ b/src/ruby/spec/compression_options_spec.rb @@ -30,6 +30,8 @@ require 'grpc' describe GRPC::Core::CompressionOptions do + # Note these constants should be updated according to what the core lib does. + # Names of supported compression algorithms and their internal enum values ALGORITHMS = { identity: 0, @@ -45,12 +47,6 @@ describe GRPC::Core::CompressionOptions do gzip: 0x4 } - # "enabled algorithms bitset" when all compression algorithms are enabled - ALL_ENABLED_BITSET = 0x7 - - # "enabled algorithms bitset" when all compression algorithms are disabled - ALL_DISABLED_BITSET = 0x0 - # Names of valid supported compression levels and their internal enum values COMPRESS_LEVELS = { none: 0, @@ -77,20 +73,20 @@ describe GRPC::Core::CompressionOptions do eql('grpc.compression_enabled_algorithms_bitset' => 0x7)) end - it 'gives the correct channel args after everything has been disabled' do + it 'gives the correct channel args after disabling multiple algorithms' do options = GRPC::Core::CompressionOptions.new( default_algorithm: :identity, default_level: :none, - disabled_algorithms: ALGORITHMS.keys + disabled_algorithms: [:gzip, :deflate] ) channel_arg_hash = options.to_hash expect(channel_arg_hash['grpc.default_compression_algorithm']).to eq(0) expect(channel_arg_hash['grpc.default_compression_level']).to eq(0) - # Don't care if the "identity" algorithm bit is set or unset bitset = channel_arg_hash['grpc.compression_enabled_algorithms_bitset'] - expect(bitset & ~ALGORITHM_BITS[:identity]).to eq(0) + expect(bitset & ALGORITHM_BITS[:gzip]).to eq(0) + expect(bitset & ALGORITHM_BITS[:deflate]).to eq(0) end it 'gives correct channel args with all args set' do @@ -100,13 +96,16 @@ describe GRPC::Core::CompressionOptions do disabled_algorithms: [:deflate] ) - expected_bitset = ALL_ENABLED_BITSET & ~ALGORITHM_BITS[:deflate] + channel_arg_hash = options.to_hash - expect(options.to_hash).to( - eql('grpc.default_compression_algorithm' => ALGORITHMS[:gzip], - 'grpc.default_compression_level' => COMPRESS_LEVELS[:low], - 'grpc.compression_enabled_algorithms_bitset' => expected_bitset) - ) + actual_bitset = channel_arg_hash[ + 'grpc.compression_enabled_algorithms_bitset'] + default_algorithm = channel_arg_hash['grpc.default_compression_algorithm'] + default_level = channel_arg_hash['grpc.default_compression_level'] + + expect(actual_bitset & ALGORITHM_BITS[:deflate]).to eq(0) + expect(default_algorithm).to eq(ALGORITHMS[:gzip]) + expect(default_level).to eq(COMPRESS_LEVELS[:low]) end it 'gives correct channel args when no algorithms are disabled' do @@ -115,11 +114,17 @@ describe GRPC::Core::CompressionOptions do default_level: :high ) - expect(options.to_hash).to( - eql('grpc.default_compression_algorithm' => ALGORITHMS[:identity], - 'grpc.default_compression_level' => COMPRESS_LEVELS[:high], - 'grpc.compression_enabled_algorithms_bitset' => ALL_ENABLED_BITSET) - ) + channel_arg_hash = options.to_hash + + actual_bitset = channel_arg_hash[ + 'grpc.compression_enabled_algorithms_bitset'] + default_algorithm = channel_arg_hash['grpc.default_compression_algorithm'] + default_level = channel_arg_hash['grpc.default_compression_level'] + + expect(actual_bitset & ALGORITHM_BITS[:deflate]).to_not eq(0) + expect(actual_bitset & ALGORITHM_BITS[:gzip]).to_not eq(0) + expect(default_algorithm).to eq(ALGORITHMS[:identity]) + expect(default_level).to eq(COMPRESS_LEVELS[:high]) end end @@ -290,7 +295,9 @@ describe GRPC::Core::CompressionOptions do describe '#enabled_algoritms_bitset' do it 'should respond to not disabling any algorithms' do options = GRPC::Core::CompressionOptions.new - expect(options.enabled_algorithms_bitset).to eq(ALL_ENABLED_BITSET) + actual_bitset = options.enabled_algorithms_bitset + expect(actual_bitset & ALGORITHM_BITS[:gzip]).to_not eq(0) + expect(actual_bitset & ALGORITHM_BITS[:deflate]).to_not eq(0) end it 'should respond to disabling one algorithm' do @@ -301,8 +308,11 @@ describe GRPC::Core::CompressionOptions do it 'should respond to disabling multiple algorithms' do options = GRPC::Core::CompressionOptions.new( - disabled_algorithms: ALGORITHMS.keys) - expect(options.enabled_algorithms_bitset).to eql(ALL_DISABLED_BITSET) + disabled_algorithms: [:gzip, :deflate]) + + actual_bitset = options.enabled_algorithms_bitset + expect(actual_bitset & ALGORITHM_BITS[:gzip]).to eq(0) + expect(actual_bitset & ALGORITHM_BITS[:deflate]).to eq(0) end end end From 7c55ab090a952b90b79324fd163feb2bc99a0724 Mon Sep 17 00:00:00 2001 From: Alex Polcyn Date: Mon, 11 Jul 2016 23:14:32 -0700 Subject: [PATCH 06/10] removed unnecessary public methods removed tests of non api methods --- src/ruby/ext/grpc/rb_compression_options.c | 140 ++---------- src/ruby/spec/compression_options_spec.rb | 252 ++++----------------- 2 files changed, 65 insertions(+), 327 deletions(-) diff --git a/src/ruby/ext/grpc/rb_compression_options.c b/src/ruby/ext/grpc/rb_compression_options.c index 02bb3a5ddff..0a3a215b1ca 100644 --- a/src/ruby/ext/grpc/rb_compression_options.c +++ b/src/ruby/ext/grpc/rb_compression_options.c @@ -148,18 +148,9 @@ grpc_compression_level grpc_rb_compression_options_level_name_to_value_internal( rb_raise(rb_eArgError, "Unrecognized compression level name." "Valid compression level names are none, low, medium, and high."); -} - -/* Wrapper over grpc_rb_compression_options_level_name_to_value available for - * use or testing. - * Raises an exception for unrecognized level names. */ -VALUE grpc_rb_compression_options_level_name_to_value(VALUE self, - VALUE level_name) { - (void)self; - Check_Type(level_name, T_SYMBOL); - return INT2NUM((int)grpc_rb_compression_options_level_name_to_value_internal( - level_name)); + /* Dummy return statement. */ + return GRPC_COMPRESS_LEVEL_NONE; } /* Sets the default compression level, given the name of a compression level. @@ -199,18 +190,6 @@ void grpc_rb_compression_options_algorithm_name_to_value_internal( } } -/* Wrapper around algorithm_name_to_value_internal function available for use or - * testing. */ -VALUE grpc_rb_compression_options_algorithm_name_to_value( - VALUE self, VALUE algorithm_name) { - grpc_compression_algorithm algorithm_value; - (void)self; - grpc_rb_compression_options_algorithm_name_to_value_internal(&algorithm_value, - algorithm_name); - - return INT2NUM((int)algorithm_value); -} - /* Indicates whether a given algorithm is enabled on this instance, given the * readable algorithm name. */ VALUE grpc_rb_compression_options_is_algorithm_enabled(VALUE self, @@ -307,15 +286,6 @@ VALUE grpc_rb_compression_options_level_value_to_name_internal( } } -/* Wrapper of internal method that makes it available for use and testing. */ -VALUE grpc_rb_compression_options_level_value_to_name(VALUE self, - VALUE compression_value) { - (void)self; - Check_Type(compression_value, T_FIXNUM); - return grpc_rb_compression_options_level_value_to_name_internal( - (grpc_compression_level)NUM2INT(compression_value)); -} - /* Converts an algorithm internal enum value to a readable name. * Fails if the enum value is invalid. */ VALUE grpc_rb_compression_options_algorithm_value_to_name_internal( @@ -329,59 +299,21 @@ VALUE grpc_rb_compression_options_algorithm_value_to_name_internal( return ID2SYM(rb_intern(algorithm_name)); } -/* Wrapper of algorithm_to_name internal function available for ues and testing. - */ -VALUE grpc_rb_compression_options_algorithm_value_to_name( - VALUE self, VALUE algorithm_value) { - grpc_compression_algorithm algorithm_internal_value = - (grpc_compression_algorithm)NUM2INT(algorithm_value); - (void)self; - - return grpc_rb_compression_options_algorithm_value_to_name_internal( - algorithm_internal_value); -} - -/* Gets the internal value of the default compression level that is to be passed - * to the GRPC core as a channel argument value. - * A nil return value means that it hasn't been set. */ -VALUE grpc_rb_compression_options_get_default_algorithm_internal_value( - VALUE self) { - grpc_rb_compression_options *wrapper = NULL; - - TypedData_Get_Struct(self, grpc_rb_compression_options, - &grpc_rb_compression_options_data_type, wrapper); - - if (wrapper->wrapped->default_algorithm.is_set) { - return INT2NUM(wrapper->wrapped->default_algorithm.algorithm); - } - return Qnil; -} - /* Gets the readable name of the default algorithm if one has been set. * Returns nil if no algorithm has been set. */ VALUE grpc_rb_compression_options_get_default_algorithm(VALUE self) { - VALUE algorithm_value = - grpc_rb_compression_options_get_default_algorithm_internal_value(self); - - if (RTEST(algorithm_value)) { - return grpc_rb_compression_options_algorithm_value_to_name(self, - algorithm_value); - } - - return Qnil; -} - -/* Gets the internal enum value of the default algorithm if one has been set. - * Returns nil if no default algorithm has been set. */ -VALUE grpc_rb_compression_options_get_default_level_internal_value(VALUE self) { + grpc_compression_algorithm internal_value; grpc_rb_compression_options *wrapper = NULL; TypedData_Get_Struct(self, grpc_rb_compression_options, &grpc_rb_compression_options_data_type, wrapper); - if (wrapper->wrapped->default_level.is_set) { - return INT2NUM((int)wrapper->wrapped->default_level.level); + if (wrapper->wrapped->default_algorithm.is_set) { + internal_value = wrapper->wrapped->default_algorithm.algorithm; + return grpc_rb_compression_options_algorithm_value_to_name_internal( + internal_value); } + return Qnil; } @@ -390,11 +322,13 @@ VALUE grpc_rb_compression_options_get_default_level_internal_value(VALUE self) { * A nil return value means that it hasn't been set. */ VALUE grpc_rb_compression_options_get_default_level(VALUE self) { grpc_compression_level internal_value; - VALUE ruby_value = - grpc_rb_compression_options_get_default_level_internal_value(self); + grpc_rb_compression_options *wrapper = NULL; + + TypedData_Get_Struct(self, grpc_rb_compression_options, + &grpc_rb_compression_options_data_type, wrapper); - if (RTEST(ruby_value)) { - internal_value = (grpc_compression_level)NUM2INT(ruby_value); + if (wrapper->wrapped->default_level.is_set) { + internal_value = wrapper->wrapped->default_level.level; return grpc_rb_compression_options_level_value_to_name_internal( internal_value); } @@ -403,7 +337,7 @@ VALUE grpc_rb_compression_options_get_default_level(VALUE self) { } /* Gets a list of the disabled algorithms as readable names. - * Returns an empty list of no algorithms have been disabled. */ + * Returns an empty list if no algorithms have been disabled. */ VALUE grpc_rb_compression_options_get_disabled_algorithms(VALUE self) { VALUE disabled_algorithms = rb_ary_new(); grpc_compression_algorithm internal_value; @@ -424,16 +358,6 @@ VALUE grpc_rb_compression_options_get_disabled_algorithms(VALUE self) { return disabled_algorithms; } -/* Provides a bitset as a ruby number that is suitable to pass to - * the GRPC core as a channel argument to enable compression algorithms. */ -VALUE grpc_rb_compression_options_get_enabled_algorithms_bitset(VALUE self) { - grpc_rb_compression_options *wrapper = NULL; - - TypedData_Get_Struct(self, grpc_rb_compression_options, - &grpc_rb_compression_options_data_type, wrapper); - return INT2NUM((int)wrapper->wrapped->enabled_algorithms_bitset); -} - /* Initializes the compression options wrapper. * Takes an optional hash parameter. * @@ -511,11 +435,6 @@ void Init_grpc_compression_options() { rb_define_method(grpc_rb_cCompressionOptions, "initialize", grpc_rb_compression_options_init, -1); - /* Gets the bitset of enabled algorithms. */ - rb_define_method(grpc_rb_cCompressionOptions, "enabled_algorithms_bitset", - grpc_rb_compression_options_get_enabled_algorithms_bitset, - 0); - /* Methods for getting the default algorithm, default level, and disabled * algorithms as readable names. */ rb_define_method(grpc_rb_cCompressionOptions, "default_algorithm", @@ -525,38 +444,11 @@ void Init_grpc_compression_options() { rb_define_method(grpc_rb_cCompressionOptions, "disabled_algorithms", grpc_rb_compression_options_get_disabled_algorithms, 0); - /* Methods for getting the internal enum default algorithm and level enum - * values of an instance. */ - rb_define_method( - grpc_rb_cCompressionOptions, "default_algorithm_internal_value", - grpc_rb_compression_options_get_default_algorithm_internal_value, 0); - rb_define_method(grpc_rb_cCompressionOptions, "default_level_internal_value", - grpc_rb_compression_options_get_default_level_internal_value, - 0); - /* Determines whether or not an algorithm is enabled, given a readable * algorithm name.*/ - rb_define_method(grpc_rb_cCompressionOptions, "is_algorithm_enabled", + rb_define_method(grpc_rb_cCompressionOptions, "algorithm_enabled?", grpc_rb_compression_options_is_algorithm_enabled, 1); - /* Methods for converting to and from algorithm enum values and their readable - * names. */ - rb_define_singleton_method( - grpc_rb_cCompressionOptions, "algorithm_name_to_value", - grpc_rb_compression_options_algorithm_name_to_value, 1); - rb_define_singleton_method( - grpc_rb_cCompressionOptions, "algorithm_value_to_name", - grpc_rb_compression_options_algorithm_value_to_name, 1); - - /* Methods for converting to and from compression level enum values and their - * readable names. */ - rb_define_singleton_method(grpc_rb_cCompressionOptions, "level_name_to_value", - grpc_rb_compression_options_level_name_to_value, - 1); - rb_define_singleton_method(grpc_rb_cCompressionOptions, "level_value_to_name", - grpc_rb_compression_options_level_value_to_name, - 1); - /* Provides a hash of the compression settings suitable * for passing to server or channel args. */ rb_define_method(grpc_rb_cCompressionOptions, "to_hash", diff --git a/src/ruby/spec/compression_options_spec.rb b/src/ruby/spec/compression_options_spec.rb index 7fa416ef095..dbd7e592947 100644 --- a/src/ruby/spec/compression_options_spec.rb +++ b/src/ruby/spec/compression_options_spec.rb @@ -30,30 +30,14 @@ require 'grpc' describe GRPC::Core::CompressionOptions do - # Note these constants should be updated according to what the core lib does. + # Note these constants should be updated + # according to what the core lib provides. - # Names of supported compression algorithms and their internal enum values - ALGORITHMS = { - identity: 0, - deflate: 1, - gzip: 2 - } + # Names of supported compression algorithms + ALGORITHMS = [:identity, :deflate, :gzip] - # Compression algorithms and their corresponding bits in the internal - # enabled algorithms bitset for GRPC core channel args. - ALGORITHM_BITS = { - identity: 0x1, - deflate: 0x2, - gzip: 0x4 - } - - # Names of valid supported compression levels and their internal enum values - COMPRESS_LEVELS = { - none: 0, - low: 1, - medium: 2, - high: 3 - } + # Names of valid supported compression levels + COMPRESS_LEVELS = [:none, :low, :medium, :high] it 'implements to_s' do expect { GRPC::Core::CompressionOptions.new.to_s }.to_not raise_error @@ -61,70 +45,73 @@ describe GRPC::Core::CompressionOptions do it '#to_channel_arg_hash gives the same result as #to_hash' do options = GRPC::Core::CompressionOptions.new - expect(options.to_channel_arg_hash).to eql(options.to_hash) + expect(options.to_channel_arg_hash).to eq(options.to_hash) end # Test the normal call sequence of creating an instance # and then obtaining the resulting channel-arg hash that # corresponds to the compression settings of the instance - describe 'creating and converting to channel args hash' do - it 'gives the correct channel args when nothing has been adjusted yet' do - expect(GRPC::Core::CompressionOptions.new.to_hash).to( - eql('grpc.compression_enabled_algorithms_bitset' => 0x7)) + describe 'creating, reading, and converting to channel args hash' do + it 'works when no optional args were provided' do + options = GRPC::Core::CompressionOptions.new + + ALGORITHMS.each do |algorithm| + expect(options.algorithm_enabled?(algorithm)).to be true + end + + expect(options.disabled_algorithms).to be_empty + expect(options.default_algorithm).to be nil + expect(options.default_level).to be nil + expect(options.to_hash).to be_instance_of(Hash) end - it 'gives the correct channel args after disabling multiple algorithms' do + it 'works when disabling multiple algorithms' do options = GRPC::Core::CompressionOptions.new( default_algorithm: :identity, default_level: :none, disabled_algorithms: [:gzip, :deflate] ) - channel_arg_hash = options.to_hash - expect(channel_arg_hash['grpc.default_compression_algorithm']).to eq(0) - expect(channel_arg_hash['grpc.default_compression_level']).to eq(0) + [:gzip, :deflate].each do |algorithm| + expect(options.algorithm_enabled?(algorithm)).to be false + expect(options.disabled_algorithms.include?(algorithm)).to be true + end - bitset = channel_arg_hash['grpc.compression_enabled_algorithms_bitset'] - expect(bitset & ALGORITHM_BITS[:gzip]).to eq(0) - expect(bitset & ALGORITHM_BITS[:deflate]).to eq(0) + expect(options.default_algorithm).to be(:identity) + expect(options.default_level).to be(:none) + expect(options.to_hash).to be_instance_of(Hash) end - it 'gives correct channel args with all args set' do + it 'works when all optional args have been set' do options = GRPC::Core::CompressionOptions.new( default_algorithm: :gzip, default_level: :low, disabled_algorithms: [:deflate] ) - channel_arg_hash = options.to_hash - - actual_bitset = channel_arg_hash[ - 'grpc.compression_enabled_algorithms_bitset'] - default_algorithm = channel_arg_hash['grpc.default_compression_algorithm'] - default_level = channel_arg_hash['grpc.default_compression_level'] + expect(options.algorithm_enabled?(:deflate)).to be false + expect(options.algorithm_enabled?(:gzip)).to be true + expect(options.disabled_algorithms).to eq([:deflate]) - expect(actual_bitset & ALGORITHM_BITS[:deflate]).to eq(0) - expect(default_algorithm).to eq(ALGORITHMS[:gzip]) - expect(default_level).to eq(COMPRESS_LEVELS[:low]) + expect(options.default_algorithm).to be(:gzip) + expect(options.default_level).to be(:low) + expect(options.to_hash).to be_instance_of(Hash) end - it 'gives correct channel args when no algorithms are disabled' do + it 'doesnt fail when no algorithms are disabled' do options = GRPC::Core::CompressionOptions.new( default_algorithm: :identity, default_level: :high ) - channel_arg_hash = options.to_hash - - actual_bitset = channel_arg_hash[ - 'grpc.compression_enabled_algorithms_bitset'] - default_algorithm = channel_arg_hash['grpc.default_compression_algorithm'] - default_level = channel_arg_hash['grpc.default_compression_level'] + ALGORITHMS.each do |algorithm| + expect(options.algorithm_enabled?(algorithm)).to be(true) + end - expect(actual_bitset & ALGORITHM_BITS[:deflate]).to_not eq(0) - expect(actual_bitset & ALGORITHM_BITS[:gzip]).to_not eq(0) - expect(default_algorithm).to eq(ALGORITHMS[:identity]) - expect(default_level).to eq(COMPRESS_LEVELS[:high]) + expect(options.disabled_algorithms).to be_empty + expect(options.default_algorithm).to be(:identity) + expect(options.default_level).to be(:high) + expect(options.to_hash).to be_instance_of(Hash) end end @@ -140,179 +127,38 @@ describe GRPC::Core::CompressionOptions do end end - describe '#level_name_to_value' do - COMPRESS_LEVELS.each_pair do |name, internal_value| - it "should return value #{internal_value} for level #{name}" do - actual_value = GRPC::Core::CompressionOptions.level_name_to_value(name) - expect(actual_value).to eq(internal_value) - end - end - - [:gzip, :deflate, :any, Object.new, 'none', 'low', 1].each do |name| - it "should fail for parameter #{name} of class #{name.class}" do - blk = proc do - GRPC::Core::CompressionOptions.level_name_to_value(name) - end - expect { blk.call }.to raise_error - end - end - end - - describe '#level_value_to_name' do - COMPRESS_LEVELS.each_pair do |name, internal_value| - it "should return level name #{name} for value #{internal_value}" do - actual_name = GRPC::Core::CompressionOptions.level_value_to_name( - internal_value) - expect(actual_name).to eq(name) - end - end - it 'should give the correct internal values from compression level names' do - cls = GRPC::Core::CompressionOptions - COMPRESS_LEVELS.each_pair do |name, internal_value| - expect(cls.level_value_to_name(internal_value)).to eq(name) - end - end - - [:gzip, :any, Object.new, '1', :low].each do |name| - it "should fail for parameter #{name} of class #{name.class}" do - blk = proc do - GRPC::Core::CompressionOptions.level_value_to_name(name) - end - expect { blk.call }.to raise_error - end - end - end - - describe '#algorithm_name_to_value' do - it 'should give the correct internal values from algorithm names' do - cls = GRPC::Core::CompressionOptions - ALGORITHMS.each_pair do |name, internal_value| - expect(cls.algorithm_name_to_value(name)).to eq(internal_value) - end - end - - ['gzip', 'deflate', :any, Object.new, :none, :low, 1].each do |name| - it "should fail for parameter #{name} of class #{name.class}" do - blk = proc do - GRPC::Core::CompressionOptions.algorithm_name_to_value(name) - end - expect { blk.call }.to raise_error - end - end - end - - describe '#algorithm_value_to_name' do - it 'should give the correct internal values from algorithm names' do - cls = GRPC::Core::CompressionOptions - ALGORITHMS.each_pair do |name, internal_value| - expect(cls.algorithm_value_to_name(internal_value)).to eq(name) - end - end - - ['gzip', :deflate, :any, Object.new, :low, '1'].each do |value| - it "should fail for parameter #{value} of class #{value.class}" do - blk = proc do - GRPC::Core::CompressionOptions.algorithm_value_to_name(value) - end - expect { blk.call }.to raise_error - end - end - end - - describe '#default_algorithm and #default_algorithm_internal_value' do - it 'can set the default algorithm and then read it back out' do - ALGORITHMS.each_pair do |name, internal_value| - options = GRPC::Core::CompressionOptions.new(default_algorithm: name) - expect(options.default_algorithm).to eq(name) - expect(options.default_algorithm_internal_value).to eq(internal_value) - end - end - + describe '#default_algorithm' do it 'returns nil if unset' do options = GRPC::Core::CompressionOptions.new - expect(options.default_algorithm).to be_nil - expect(options.default_algorithm_internal_value).to be_nil + expect(options.default_algorithm).to be(nil) end end - describe '#default_level and #default_level_internal_value' do - it 'can set the default level and read it back out' do - COMPRESS_LEVELS.each_pair do |name, internal_value| - options = GRPC::Core::CompressionOptions.new(default_level: name) - expect(options.default_level).to eq(name) - expect(options.default_level_internal_value).to eq(internal_value) - end - end - + describe '#default_level' do it 'returns nil if unset' do options = GRPC::Core::CompressionOptions.new - expect(options.default_level).to be_nil - expect(options.default_level_internal_value).to be_nil + expect(options.default_level).to be(nil) end end describe '#disabled_algorithms' do - it 'can set the disabled algorithms and read them back out' do - options = GRPC::Core::CompressionOptions.new( - disabled_algorithms: [:gzip, :deflate]) - - [:gzip, :deflate].each do |name| - expect(options.disabled_algorithms.include?(name)).to eq(true) - end - expect(options.disabled_algorithms.size).to eq(2) - end - it 'returns an empty list if no algorithms were disabled' do options = GRPC::Core::CompressionOptions.new - expect(options.disabled_algorithms).to eq([]) + expect(options.disabled_algorithms).to be_empty end end - describe '#is_algorithm_enabled' do - it 'returns true if the algorithm is valid and not disabled' do - options = GRPC::Core::CompressionOptions.new(disabled_algorithms: [:gzip]) - expect(options.is_algorithm_enabled(:deflate)).to eq(true) - end - - it 'returns false if the algorithm is valid and disabled' do - options = GRPC::Core::CompressionOptions.new(disabled_algorithms: [:gzip]) - expect(options.is_algorithm_enabled(:gzip)).to eq(false) - end - + describe '#algorithm_enabled?' do [:none, :any, 'gzip', Object.new, 1].each do |name| it "should fail for parameter ${name} of class #{name.class}" do options = GRPC::Core::CompressionOptions.new( disabled_algorithms: [:gzip]) blk = proc do - options.is_algorithm_enabled(name) + options.algorithm_enabled?(name) end expect { blk.call }.to raise_error end end end - - describe '#enabled_algoritms_bitset' do - it 'should respond to not disabling any algorithms' do - options = GRPC::Core::CompressionOptions.new - actual_bitset = options.enabled_algorithms_bitset - expect(actual_bitset & ALGORITHM_BITS[:gzip]).to_not eq(0) - expect(actual_bitset & ALGORITHM_BITS[:deflate]).to_not eq(0) - end - - it 'should respond to disabling one algorithm' do - options = GRPC::Core::CompressionOptions.new( - disabled_algorithms: [:gzip]) - expect(options.enabled_algorithms_bitset & ALGORITHM_BITS[:gzip]).to eq(0) - end - - it 'should respond to disabling multiple algorithms' do - options = GRPC::Core::CompressionOptions.new( - disabled_algorithms: [:gzip, :deflate]) - - actual_bitset = options.enabled_algorithms_bitset - expect(actual_bitset & ALGORITHM_BITS[:gzip]).to eq(0) - expect(actual_bitset & ALGORITHM_BITS[:deflate]).to eq(0) - end - end end From cdff92f02ea04ef238b6393a843e3605060efde1 Mon Sep 17 00:00:00 2001 From: Alexander Polcyn Date: Tue, 12 Jul 2016 12:30:30 -0700 Subject: [PATCH 07/10] added ruby client compression interop tests --- src/ruby/bin/math_services.rb | 6 +- src/ruby/ext/grpc/rb_call.c | 8 + .../pb/src/proto/grpc/testing/messages.rb | 18 +- src/ruby/pb/test/client.rb | 174 +++++++++++++++++- src/ruby/pb/test/proto/empty.rb | 15 -- src/ruby/pb/test/proto/messages.rb | 80 -------- src/ruby/pb/test/proto/test.rb | 14 -- src/ruby/pb/test/proto/test_services.rb | 64 ------- src/ruby/pb/test/server.rb | 6 +- .../qps/src/proto/grpc/testing/messages.rb | 18 +- tools/run_tests/run_interop_tests.py | 2 +- 11 files changed, 201 insertions(+), 204 deletions(-) delete mode 100644 src/ruby/pb/test/proto/empty.rb delete mode 100644 src/ruby/pb/test/proto/messages.rb delete mode 100644 src/ruby/pb/test/proto/test.rb delete mode 100644 src/ruby/pb/test/proto/test_services.rb diff --git a/src/ruby/bin/math_services.rb b/src/ruby/bin/math_services.rb index 34c36abddae..2b97602b6fb 100755 --- a/src/ruby/bin/math_services.rb +++ b/src/ruby/bin/math_services.rb @@ -44,15 +44,15 @@ module Math self.unmarshal_class_method = :decode self.service_name = 'math.Math' - # Div divides args.dividend by args.divisor and returns the quotient and - # remainder. + # Div divides DivArgs.dividend by DivArgs.divisor and returns the quotient + # and remainder. rpc :Div, DivArgs, DivReply # DivMany accepts an arbitrary number of division args from the client stream # and sends back the results in the reply stream. The stream continues until # the client closes its end; the server does the same after sending all the # replies. The stream ends immediately if either end aborts. rpc :DivMany, stream(DivArgs), stream(DivReply) - # Fib generates numbers in the Fibonacci sequence. If args.limit > 0, Fib + # Fib generates numbers in the Fibonacci sequence. If FibArgs.limit > 0, Fib # generates up to limit numbers; otherwise it continues until the call is # canceled. Unlike Fib above, Fib has no final FibReply. rpc :Fib, FibArgs, stream(Num) diff --git a/src/ruby/ext/grpc/rb_call.c b/src/ruby/ext/grpc/rb_call.c index 21261244436..67a42af619b 100644 --- a/src/ruby/ext/grpc/rb_call.c +++ b/src/ruby/ext/grpc/rb_call.c @@ -38,6 +38,7 @@ #include #include +#include #include "rb_byte_buffer.h" #include "rb_call_credentials.h" @@ -910,6 +911,12 @@ static void Init_grpc_op_codes() { UINT2NUM(GRPC_OP_RECV_CLOSE_ON_SERVER)); } +static void Init_grpc_metadata_keys() { + VALUE grpc_rb_mMetadataKeys = rb_define_module_under(grpc_rb_mGrpcCore, "MetadataKeys"); + rb_define_const(grpc_rb_mMetadataKeys, "COMPRESSION_REQUEST_ALGORITHM", + rb_str_new2(GRPC_COMPRESSION_REQUEST_ALGORITHM_MD_KEY)); +} + void Init_grpc_call() { /* CallError inherits from Exception to signal that it is non-recoverable */ grpc_rb_eCallError = @@ -972,6 +979,7 @@ void Init_grpc_call() { Init_grpc_error_codes(); Init_grpc_op_codes(); Init_grpc_write_flags(); + Init_grpc_metadata_keys(); } /* Gets the call from the ruby object */ diff --git a/src/ruby/pb/src/proto/grpc/testing/messages.rb b/src/ruby/pb/src/proto/grpc/testing/messages.rb index 2bdfe0eade3..e27ccd0dc04 100644 --- a/src/ruby/pb/src/proto/grpc/testing/messages.rb +++ b/src/ruby/pb/src/proto/grpc/testing/messages.rb @@ -4,6 +4,9 @@ require 'google/protobuf' Google::Protobuf::DescriptorPool.generated_pool.build do + add_message "grpc.testing.BoolValue" do + optional :value, :bool, 1 + end add_message "grpc.testing.Payload" do optional :type, :enum, 1, "grpc.testing.PayloadType" optional :body, :bytes, 2 @@ -18,8 +21,9 @@ Google::Protobuf::DescriptorPool.generated_pool.build do optional :payload, :message, 3, "grpc.testing.Payload" optional :fill_username, :bool, 4 optional :fill_oauth_scope, :bool, 5 - optional :response_compression, :enum, 6, "grpc.testing.CompressionType" + optional :response_compressed, :message, 6, "grpc.testing.BoolValue" optional :response_status, :message, 7, "grpc.testing.EchoStatus" + optional :expect_compressed, :message, 8, "grpc.testing.BoolValue" end add_message "grpc.testing.SimpleResponse" do optional :payload, :message, 1, "grpc.testing.Payload" @@ -28,6 +32,7 @@ Google::Protobuf::DescriptorPool.generated_pool.build do end add_message "grpc.testing.StreamingInputCallRequest" do optional :payload, :message, 1, "grpc.testing.Payload" + optional :expect_compressed, :message, 2, "grpc.testing.BoolValue" end add_message "grpc.testing.StreamingInputCallResponse" do optional :aggregated_payload_size, :int32, 1 @@ -35,12 +40,12 @@ Google::Protobuf::DescriptorPool.generated_pool.build do add_message "grpc.testing.ResponseParameters" do optional :size, :int32, 1 optional :interval_us, :int32, 2 + optional :compressed, :message, 3, "grpc.testing.BoolValue" end add_message "grpc.testing.StreamingOutputCallRequest" do optional :response_type, :enum, 1, "grpc.testing.PayloadType" repeated :response_parameters, :message, 2, "grpc.testing.ResponseParameters" optional :payload, :message, 3, "grpc.testing.Payload" - optional :response_compression, :enum, 6, "grpc.testing.CompressionType" optional :response_status, :message, 7, "grpc.testing.EchoStatus" end add_message "grpc.testing.StreamingOutputCallResponse" do @@ -55,18 +60,12 @@ Google::Protobuf::DescriptorPool.generated_pool.build do end add_enum "grpc.testing.PayloadType" do value :COMPRESSABLE, 0 - value :UNCOMPRESSABLE, 1 - value :RANDOM, 2 - end - add_enum "grpc.testing.CompressionType" do - value :NONE, 0 - value :GZIP, 1 - value :DEFLATE, 2 end end module Grpc module Testing + BoolValue = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.BoolValue").msgclass Payload = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.Payload").msgclass EchoStatus = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.EchoStatus").msgclass SimpleRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.SimpleRequest").msgclass @@ -79,6 +78,5 @@ module Grpc ReconnectParams = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.ReconnectParams").msgclass ReconnectInfo = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.ReconnectInfo").msgclass PayloadType = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.PayloadType").enummodule - CompressionType = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.CompressionType").enummodule end end diff --git a/src/ruby/pb/test/client.rb b/src/ruby/pb/test/client.rb index 066a7bb90f1..73494e6aab1 100755 --- a/src/ruby/pb/test/client.rb +++ b/src/ruby/pb/test/client.rb @@ -52,9 +52,9 @@ require_relative '../../lib/grpc' require 'googleauth' require 'google/protobuf' -require_relative 'proto/empty' -require_relative 'proto/messages' -require_relative 'proto/test_services' +require_relative '../src/proto/grpc/testing/empty' +require_relative '../src/proto/grpc/testing/messages' +require_relative '../src/proto/grpc/testing/test_services' AUTH_ENV = Google::Auth::CredentialsLoader::ENV_VAR @@ -111,6 +111,18 @@ end # creates a test stub that accesses host:port securely. def create_stub(opts) address = "#{opts.host}:#{opts.port}" + + # Provide channel args that request compression by default + # for compression interop tests + if ['client_compressed_unary', + 'client_compressed_streaming'].include?(opts.test_case) + compression_options = + GRPC::Core::CompressionOptions.new(default_algorithm: :gzip) + compression_channel_args = compression_options.to_channel_arg_hash + else + compression_channel_args = {} + end + if opts.secure creds = ssl_creds(opts.use_test_ca) stub_opts = { @@ -145,10 +157,15 @@ def create_stub(opts) end GRPC.logger.info("... connecting securely to #{address}") + stub_opts[:channel_args].merge!(compression_channel_args) Grpc::Testing::TestService::Stub.new(address, creds, **stub_opts) else GRPC.logger.info("... connecting insecurely to #{address}") - Grpc::Testing::TestService::Stub.new(address, :this_channel_is_insecure) + Grpc::Testing::TestService::Stub.new( + address, + :this_channel_is_insecure, + channel_args: compression_channel_args + ) end end @@ -216,10 +233,41 @@ class BlockingEnumerator end end +# Wraps a Queue to yield items to it. +# Intended to be used to wrap a call_op as well, and to adjust +# the write flag of the call_op in between messages yielded to it. +class WriteFlagSettingEnumeratorQueue + extend Forwardable + def_delegators :@q, :push + attr_accessor :call_op + + def initialize(sentinel) + @q = Queue.new + @sentinel = sentinel + @received_notes = {} + end + + def each_item + return enum_for(:each_item) unless block_given? + loop do + request_and_write_flag = @q.pop + break if request_and_write_flag.equal?(@sentinel) + fail request_and_write_flag if + request_and_write_flag.is_a? Exception + + @call_op.write_flag = request_and_write_flag[:write_flag] if + request_and_write_flag[:write_flag] + + yield request_and_write_flag[:request] + end + end +end + # defines methods corresponding to each interop test case. class NamedTests include Grpc::Testing include Grpc::Testing::PayloadType + include GRPC::Core::MetadataKeys def initialize(stub, args) @stub = stub @@ -235,6 +283,48 @@ class NamedTests perform_large_unary end + def client_compressed_unary + # first request used also for the probe + req_size, wanted_response_size = 271_828, 314_159 + expect_compressed = BoolValue.new(value: true) + payload = Payload.new(type: :COMPRESSABLE, body: nulls(req_size)) + req = SimpleRequest.new(response_type: :COMPRESSABLE, + response_size: wanted_response_size, + payload: payload, + expect_compressed: expect_compressed) + + # send a probe to see if CompressedResponse is supported on the server + send_probe_for_compressed_request_support do + request_uncompressed_args = { + COMPRESSION_REQUEST_ALGORITHM => 'identity' + } + @stub.unary_call(req, metadata: request_uncompressed_args) + end + + # make a call with a compressed message + resp = @stub.unary_call(req) + assert('Expected second unary call with compression to work') do + resp.payload.body.length == wanted_response_size + end + + # make a call with an uncompressed message + stub_options = { + COMPRESSION_REQUEST_ALGORITHM => 'identity' + } + + req = SimpleRequest.new( + response_type: :COMPRESSABLE, + response_size: wanted_response_size, + payload: payload, + expect_compressed: BoolValue.new(value: false) + ) + + resp = @stub.unary_call(req, metadata: stub_options) + assert('Expected second unary call with compression to work') do + resp.payload.body.length == wanted_response_size + end + end + def service_account_creds # ignore this test if the oauth options are not set if @args.oauth_scope.nil? @@ -309,6 +399,59 @@ class NamedTests end end + def client_compressed_streaming + # first request used also by the probe + first_request = StreamingInputCallRequest.new( + payload: Payload.new(type: :COMPRESSABLE, body: nulls(27_182)), + expect_compressed: BoolValue.new(value: true) + ) + + # send a probe to see if CompressedResponse is supported on the server + send_probe_for_compressed_request_support do + request_uncompressed_args = { + COMPRESSION_REQUEST_ALGORITHM => 'identity' + } + @stub.streaming_input_call([first_request], + metadata: request_uncompressed_args) + end + + # Create the deferred enumerator, start the streaming call with it, and + # set the enumerator's call_op to the call. + requests = WriteFlagSettingEnumeratorQueue.new(self) + call_op = @stub.streaming_input_call(requests.each_item, + return_op: true) + requests.call_op = call_op + + request_thread = Thread.new do + call_op.execute + end + + # send a compressed request + requests.push({ request: first_request }) + + # send an uncompressed request + second_request = StreamingInputCallRequest.new( + payload: Payload.new(type: :COMPRESSABLE, body: nulls(45_904)), + expect_compressed: BoolValue.new(value: false) + ) + + requests.push( + { request: second_request, + write_flag: GRPC::Core::WriteFlags::NO_COMPRESS + }) + + # Close the input stream + requests.push(self) + + resp = request_thread.value + + wanted_aggregate_size = 73_086 + + assert("#{__callee__}: aggregate payload size is incorrect") do + wanted_aggregate_size == resp.aggregated_payload_size + end + end + def server_streaming msg_sizes = [31_415, 9, 2653, 58_979] response_spec = msg_sizes.map { |s| ResponseParameters.new(size: s) } @@ -415,6 +558,29 @@ class NamedTests end resp end + + # Send probing message for compressed request on the server, to see + # if it's implemented. + def send_probe_for_compressed_request_support(&send_probe) + bad_status_occured = false + + begin + send_probe.call + rescue GRPC::BadStatus => e + if e.code == GRPC::Core::StatusCodes::INVALID_ARGUMENT + bad_status_occured = true + else + fail AssertionError, "Bad status received but code is #{e.code}" + end + rescue Exception => e + fail AssertionError, "Expected BadStatus. Received: #{e.inspect}" + end + + assert('CompressedRequest probe failed') do + bad_status_occured + end + end + end # Args is used to hold the command line info. diff --git a/src/ruby/pb/test/proto/empty.rb b/src/ruby/pb/test/proto/empty.rb deleted file mode 100644 index 559adcc85e7..00000000000 --- a/src/ruby/pb/test/proto/empty.rb +++ /dev/null @@ -1,15 +0,0 @@ -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: test/proto/empty.proto - -require 'google/protobuf' - -Google::Protobuf::DescriptorPool.generated_pool.build do - add_message "grpc.testing.Empty" do - end -end - -module Grpc - module Testing - Empty = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.Empty").msgclass - end -end diff --git a/src/ruby/pb/test/proto/messages.rb b/src/ruby/pb/test/proto/messages.rb deleted file mode 100644 index 5222c9824a7..00000000000 --- a/src/ruby/pb/test/proto/messages.rb +++ /dev/null @@ -1,80 +0,0 @@ -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: test/proto/messages.proto - -require 'google/protobuf' - -Google::Protobuf::DescriptorPool.generated_pool.build do - add_message "grpc.testing.Payload" do - optional :type, :enum, 1, "grpc.testing.PayloadType" - optional :body, :bytes, 2 - end - add_message "grpc.testing.EchoStatus" do - optional :code, :int32, 1 - optional :message, :string, 2 - end - add_message "grpc.testing.SimpleRequest" do - optional :response_type, :enum, 1, "grpc.testing.PayloadType" - optional :response_size, :int32, 2 - optional :payload, :message, 3, "grpc.testing.Payload" - optional :fill_username, :bool, 4 - optional :fill_oauth_scope, :bool, 5 - optional :response_compression, :enum, 6, "grpc.testing.CompressionType" - optional :response_status, :message, 7, "grpc.testing.EchoStatus" - end - add_message "grpc.testing.SimpleResponse" do - optional :payload, :message, 1, "grpc.testing.Payload" - optional :username, :string, 2 - optional :oauth_scope, :string, 3 - end - add_message "grpc.testing.StreamingInputCallRequest" do - optional :payload, :message, 1, "grpc.testing.Payload" - end - add_message "grpc.testing.StreamingInputCallResponse" do - optional :aggregated_payload_size, :int32, 1 - end - add_message "grpc.testing.ResponseParameters" do - optional :size, :int32, 1 - optional :interval_us, :int32, 2 - end - add_message "grpc.testing.StreamingOutputCallRequest" do - optional :response_type, :enum, 1, "grpc.testing.PayloadType" - repeated :response_parameters, :message, 2, "grpc.testing.ResponseParameters" - optional :payload, :message, 3, "grpc.testing.Payload" - optional :response_compression, :enum, 6, "grpc.testing.CompressionType" - optional :response_status, :message, 7, "grpc.testing.EchoStatus" - end - add_message "grpc.testing.StreamingOutputCallResponse" do - optional :payload, :message, 1, "grpc.testing.Payload" - end - add_message "grpc.testing.ReconnectInfo" do - optional :passed, :bool, 1 - repeated :backoff_ms, :int32, 2 - end - add_enum "grpc.testing.PayloadType" do - value :COMPRESSABLE, 0 - value :UNCOMPRESSABLE, 1 - value :RANDOM, 2 - end - add_enum "grpc.testing.CompressionType" do - value :NONE, 0 - value :GZIP, 1 - value :DEFLATE, 2 - end -end - -module Grpc - module Testing - Payload = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.Payload").msgclass - EchoStatus = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.EchoStatus").msgclass - SimpleRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.SimpleRequest").msgclass - SimpleResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.SimpleResponse").msgclass - StreamingInputCallRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.StreamingInputCallRequest").msgclass - StreamingInputCallResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.StreamingInputCallResponse").msgclass - ResponseParameters = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.ResponseParameters").msgclass - StreamingOutputCallRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.StreamingOutputCallRequest").msgclass - StreamingOutputCallResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.StreamingOutputCallResponse").msgclass - ReconnectInfo = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.ReconnectInfo").msgclass - PayloadType = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.PayloadType").enummodule - CompressionType = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.CompressionType").enummodule - end -end diff --git a/src/ruby/pb/test/proto/test.rb b/src/ruby/pb/test/proto/test.rb deleted file mode 100644 index 100eb6505c9..00000000000 --- a/src/ruby/pb/test/proto/test.rb +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: test/proto/test.proto - -require 'google/protobuf' - -require 'test/proto/empty' -require 'test/proto/messages' -Google::Protobuf::DescriptorPool.generated_pool.build do -end - -module Grpc - module Testing - end -end diff --git a/src/ruby/pb/test/proto/test_services.rb b/src/ruby/pb/test/proto/test_services.rb deleted file mode 100644 index 9df9cc5860b..00000000000 --- a/src/ruby/pb/test/proto/test_services.rb +++ /dev/null @@ -1,64 +0,0 @@ -# Generated by the protocol buffer compiler. DO NOT EDIT! -# Source: test/proto/test.proto for package 'grpc.testing' - -require 'grpc' -require 'test/proto/test' - -module Grpc - module Testing - module TestService - - # TODO: add proto service documentation here - class Service - - include GRPC::GenericService - - self.marshal_class_method = :encode - self.unmarshal_class_method = :decode - self.service_name = 'grpc.testing.TestService' - - rpc :EmptyCall, Empty, Empty - rpc :UnaryCall, SimpleRequest, SimpleResponse - rpc :StreamingOutputCall, StreamingOutputCallRequest, stream(StreamingOutputCallResponse) - rpc :StreamingInputCall, stream(StreamingInputCallRequest), StreamingInputCallResponse - rpc :FullDuplexCall, stream(StreamingOutputCallRequest), stream(StreamingOutputCallResponse) - rpc :HalfDuplexCall, stream(StreamingOutputCallRequest), stream(StreamingOutputCallResponse) - end - - Stub = Service.rpc_stub_class - end - module UnimplementedService - - # TODO: add proto service documentation here - class Service - - include GRPC::GenericService - - self.marshal_class_method = :encode - self.unmarshal_class_method = :decode - self.service_name = 'grpc.testing.UnimplementedService' - - rpc :UnimplementedCall, Empty, Empty - end - - Stub = Service.rpc_stub_class - end - module ReconnectService - - # TODO: add proto service documentation here - class Service - - include GRPC::GenericService - - self.marshal_class_method = :encode - self.unmarshal_class_method = :decode - self.service_name = 'grpc.testing.ReconnectService' - - rpc :Start, Empty, Empty - rpc :Stop, Empty, ReconnectInfo - end - - Stub = Service.rpc_stub_class - end - end -end diff --git a/src/ruby/pb/test/server.rb b/src/ruby/pb/test/server.rb index 088f281dc47..11ee3d465d8 100755 --- a/src/ruby/pb/test/server.rb +++ b/src/ruby/pb/test/server.rb @@ -50,9 +50,9 @@ require 'optparse' require 'grpc' -require 'test/proto/empty' -require 'test/proto/messages' -require 'test/proto/test_services' +require_relative '../src/proto/grpc/testing/empty' +require_relative '../src/proto/grpc/testing/messages' +require_relative '../src/proto/grpc/testing/test_services' # DebugIsTruncated extends the default Logger to truncate debug messages class DebugIsTruncated < Logger diff --git a/src/ruby/qps/src/proto/grpc/testing/messages.rb b/src/ruby/qps/src/proto/grpc/testing/messages.rb index 2bdfe0eade3..e27ccd0dc04 100644 --- a/src/ruby/qps/src/proto/grpc/testing/messages.rb +++ b/src/ruby/qps/src/proto/grpc/testing/messages.rb @@ -4,6 +4,9 @@ require 'google/protobuf' Google::Protobuf::DescriptorPool.generated_pool.build do + add_message "grpc.testing.BoolValue" do + optional :value, :bool, 1 + end add_message "grpc.testing.Payload" do optional :type, :enum, 1, "grpc.testing.PayloadType" optional :body, :bytes, 2 @@ -18,8 +21,9 @@ Google::Protobuf::DescriptorPool.generated_pool.build do optional :payload, :message, 3, "grpc.testing.Payload" optional :fill_username, :bool, 4 optional :fill_oauth_scope, :bool, 5 - optional :response_compression, :enum, 6, "grpc.testing.CompressionType" + optional :response_compressed, :message, 6, "grpc.testing.BoolValue" optional :response_status, :message, 7, "grpc.testing.EchoStatus" + optional :expect_compressed, :message, 8, "grpc.testing.BoolValue" end add_message "grpc.testing.SimpleResponse" do optional :payload, :message, 1, "grpc.testing.Payload" @@ -28,6 +32,7 @@ Google::Protobuf::DescriptorPool.generated_pool.build do end add_message "grpc.testing.StreamingInputCallRequest" do optional :payload, :message, 1, "grpc.testing.Payload" + optional :expect_compressed, :message, 2, "grpc.testing.BoolValue" end add_message "grpc.testing.StreamingInputCallResponse" do optional :aggregated_payload_size, :int32, 1 @@ -35,12 +40,12 @@ Google::Protobuf::DescriptorPool.generated_pool.build do add_message "grpc.testing.ResponseParameters" do optional :size, :int32, 1 optional :interval_us, :int32, 2 + optional :compressed, :message, 3, "grpc.testing.BoolValue" end add_message "grpc.testing.StreamingOutputCallRequest" do optional :response_type, :enum, 1, "grpc.testing.PayloadType" repeated :response_parameters, :message, 2, "grpc.testing.ResponseParameters" optional :payload, :message, 3, "grpc.testing.Payload" - optional :response_compression, :enum, 6, "grpc.testing.CompressionType" optional :response_status, :message, 7, "grpc.testing.EchoStatus" end add_message "grpc.testing.StreamingOutputCallResponse" do @@ -55,18 +60,12 @@ Google::Protobuf::DescriptorPool.generated_pool.build do end add_enum "grpc.testing.PayloadType" do value :COMPRESSABLE, 0 - value :UNCOMPRESSABLE, 1 - value :RANDOM, 2 - end - add_enum "grpc.testing.CompressionType" do - value :NONE, 0 - value :GZIP, 1 - value :DEFLATE, 2 end end module Grpc module Testing + BoolValue = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.BoolValue").msgclass Payload = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.Payload").msgclass EchoStatus = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.EchoStatus").msgclass SimpleRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.SimpleRequest").msgclass @@ -79,6 +78,5 @@ module Grpc ReconnectParams = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.ReconnectParams").msgclass ReconnectInfo = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.ReconnectInfo").msgclass PayloadType = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.PayloadType").enummodule - CompressionType = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.testing.CompressionType").enummodule end end diff --git a/tools/run_tests/run_interop_tests.py b/tools/run_tests/run_interop_tests.py index 13a4a49325a..b71627a9c89 100755 --- a/tools/run_tests/run_interop_tests.py +++ b/tools/run_tests/run_interop_tests.py @@ -286,7 +286,7 @@ class RubyLanguage: return {} def unimplemented_test_cases(self): - return _SKIP_ADVANCED + _SKIP_COMPRESSION + return _SKIP_ADVANCED + _SKIP_SERVER_COMPRESSION def unimplemented_test_cases_server(self): return _SKIP_ADVANCED + _SKIP_COMPRESSION From 81aab14ba654969b90b57825e18356f3bb08cf48 Mon Sep 17 00:00:00 2001 From: Alexander Polcyn Date: Tue, 12 Jul 2016 15:14:43 -0700 Subject: [PATCH 08/10] simplified client streaming compression request enumerable --- src/ruby/pb/test/client.rb | 61 +++++++++++++------------------------- 1 file changed, 20 insertions(+), 41 deletions(-) diff --git a/src/ruby/pb/test/client.rb b/src/ruby/pb/test/client.rb index 73494e6aab1..33f40c2b9d0 100755 --- a/src/ruby/pb/test/client.rb +++ b/src/ruby/pb/test/client.rb @@ -236,29 +236,18 @@ end # Wraps a Queue to yield items to it. # Intended to be used to wrap a call_op as well, and to adjust # the write flag of the call_op in between messages yielded to it. -class WriteFlagSettingEnumeratorQueue - extend Forwardable - def_delegators :@q, :push +class WriteFlagSettingStreamingInputEnumerable attr_accessor :call_op - def initialize(sentinel) - @q = Queue.new - @sentinel = sentinel - @received_notes = {} + def initialize(requests_and_write_flags) + @requests_and_write_flags = requests_and_write_flags end - def each_item - return enum_for(:each_item) unless block_given? - loop do - request_and_write_flag = @q.pop - break if request_and_write_flag.equal?(@sentinel) - fail request_and_write_flag if - request_and_write_flag.is_a? Exception - - @call_op.write_flag = request_and_write_flag[:write_flag] if - request_and_write_flag[:write_flag] - - yield request_and_write_flag[:request] + def each + @requests_and_write_flags.each do |request_and_flag| + @call_op.write_flag = request_and_flag[:write_flag] if + request_and_flag[:write_flag] + yield request_and_flag[:request] end end end @@ -415,35 +404,25 @@ class NamedTests metadata: request_uncompressed_args) end - # Create the deferred enumerator, start the streaming call with it, and - # set the enumerator's call_op to the call. - requests = WriteFlagSettingEnumeratorQueue.new(self) - call_op = @stub.streaming_input_call(requests.each_item, - return_op: true) - requests.call_op = call_op - - request_thread = Thread.new do - call_op.execute - end - - # send a compressed request - requests.push({ request: first_request }) - - # send an uncompressed request second_request = StreamingInputCallRequest.new( payload: Payload.new(type: :COMPRESSABLE, body: nulls(45_904)), expect_compressed: BoolValue.new(value: false) ) - requests.push( + # Create the requests messages and the corresponding write flags + # for each message + requests = WriteFlagSettingStreamingInputEnumerable.new([ + { request: first_request }, { request: second_request, - write_flag: GRPC::Core::WriteFlags::NO_COMPRESS - }) + write_flag: GRPC::Core::WriteFlags::NO_COMPRESS } + ]) - # Close the input stream - requests.push(self) - - resp = request_thread.value + # Create the call_op, pass it to the requests enumerable, and + # run the call + call_op = @stub.streaming_input_call(requests, + return_op: true) + requests.call_op = call_op + resp = call_op.execute wanted_aggregate_size = 73_086 From 3403565ca4f9b319261cd016a01919727ab29079 Mon Sep 17 00:00:00 2001 From: Alexander Polcyn Date: Tue, 12 Jul 2016 15:21:27 -0700 Subject: [PATCH 09/10] updated inner class comment --- src/ruby/pb/test/client.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ruby/pb/test/client.rb b/src/ruby/pb/test/client.rb index 33f40c2b9d0..6d1e735d26f 100755 --- a/src/ruby/pb/test/client.rb +++ b/src/ruby/pb/test/client.rb @@ -233,8 +233,7 @@ class BlockingEnumerator end end -# Wraps a Queue to yield items to it. -# Intended to be used to wrap a call_op as well, and to adjust +# Intended to be used to wrap a call_op, and to adjust # the write flag of the call_op in between messages yielded to it. class WriteFlagSettingStreamingInputEnumerable attr_accessor :call_op From 535b71dce43c0ab2081a9cd960ed0c4a50d8cc33 Mon Sep 17 00:00:00 2001 From: Alexander Polcyn Date: Tue, 12 Jul 2016 15:52:08 -0700 Subject: [PATCH 10/10] changed compression request streaming enumerable to set write flag on every message --- src/ruby/pb/test/client.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ruby/pb/test/client.rb b/src/ruby/pb/test/client.rb index 6d1e735d26f..4c6d441dcb6 100755 --- a/src/ruby/pb/test/client.rb +++ b/src/ruby/pb/test/client.rb @@ -244,8 +244,7 @@ class WriteFlagSettingStreamingInputEnumerable def each @requests_and_write_flags.each do |request_and_flag| - @call_op.write_flag = request_and_flag[:write_flag] if - request_and_flag[:write_flag] + @call_op.write_flag = request_and_flag[:write_flag] yield request_and_flag[:request] end end @@ -411,7 +410,8 @@ class NamedTests # Create the requests messages and the corresponding write flags # for each message requests = WriteFlagSettingStreamingInputEnumerable.new([ - { request: first_request }, + { request: first_request, + write_flag: 0 }, { request: second_request, write_flag: GRPC::Core::WriteFlags::NO_COMPRESS } ])