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