Add dependencies locally

This commit is contained in:
Ahrimdon
2024-02-27 03:09:30 -05:00
parent 1679ef60cc
commit 70e8a8502b
5698 changed files with 2770161 additions and 12 deletions

9
deps/protobuf/ruby/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
*.bundle
tags
.idea/
lib/google/protobuf_java.jar
protobuf-jruby.iml
target/
pkg/
tmp/
tests/google/

3
deps/protobuf/ruby/Gemfile vendored Normal file
View File

@ -0,0 +1,3 @@
source 'https://rubygems.org'
gemspec

111
deps/protobuf/ruby/README.md vendored Normal file
View File

@ -0,0 +1,111 @@
This directory contains the Ruby extension that implements Protocol Buffers
functionality in Ruby.
The Ruby extension makes use of generated Ruby code that defines message and
enum types in a Ruby DSL. You may write definitions in this DSL directly, but
we recommend using protoc's Ruby generation support with .proto files. The
build process in this directory only installs the extension; you need to
install protoc as well to have Ruby code generation functionality.
Installation from Gem
---------------------
In Gemfile (Please check a version of Protocol Buffers you needed [RubyGems](https://rubygems.org/gems/google-protobuf)):
gem 'google-protobuf'
Or for using this pre-packaged gem, simply install it as you would any other gem:
$ gem install [--prerelease] google-protobuf
Once the gem is installed, you may or may not need `protoc`. If you write your
message type descriptions directly in the Ruby DSL, you do not need it.
However, if you wish to generate the Ruby DSL from a `.proto` file, you will
also want to install Protocol Buffers itself, as described in this repository's
main `README` file. The version of `protoc` included in the latest release
supports the `--ruby_out` option to generate Ruby code.
A simple example of using the Ruby extension follows. More extensive
documentation may be found in the RubyDoc comments (`call-seq` tags) in the
source, and we plan to release separate, more detailed, documentation at a
later date.
```ruby
require 'google/protobuf'
# generated from my_proto_types.proto with protoc:
# $ protoc --ruby_out=. my_proto_types.proto
require 'my_proto_types'
mymessage = MyTestMessage.new(:field1 => 42, :field2 => ["a", "b", "c"])
mymessage.field1 = 43
mymessage.field2.push("d")
mymessage.field3 = SubMessage.new(:foo => 100)
encoded_data = MyTestMessage.encode(mymessage)
decoded = MyTestMessage.decode(encoded_data)
assert decoded == mymessage
puts "JSON:"
puts MyTestMessage.encode_json(mymessage)
```
Installation from Source (Building Gem)
---------------------------------------
To build this Ruby extension, you will need:
* Rake
* Bundler
* Ruby development headers
* a C compiler
To Build the JRuby extension, you will need:
* Maven
* The latest version of the protobuf java library (see ../java/README.md)
* Install JRuby via rbenv or RVM
First switch to the desired platform with rbenv or RVM.
Then install the required Ruby gems:
$ gem install bundler
$ bundle
Then build the Gem:
$ rake
$ rake clobber_package gem
$ gem install `ls pkg/google-protobuf-*.gem`
To run the specs:
$ rake test
This gem includes the upb parsing and serialization library as a single-file
amalgamation. It is up-to-date with upb git commit
`535bc2fe2f2b467f59347ffc9449e11e47791257`.
Version Number Scheme
---------------------
We are using a version number scheme that is a hybrid of Protocol Buffers'
overall version number and some Ruby-specific rules. Gem does not allow
re-uploads of a gem with the same version number, so we add a sequence number
("upload version") to the version. We also format alphabetical tags (alpha,
pre, ...) slightly differently, and we avoid hyphens. In more detail:
* First, we determine the prefix: a Protocol Buffers version "3.0.0-alpha-2"
becomes "3.0.0.alpha.2". When we release 3.0.0, this prefix will be simply
"3.0.0".
* We then append the upload version: "3.0.0.alpha.2.0" or "3.0.0.0". If we need
to upload a new version of the gem to fix an issue, the version becomes
"3.0.0.alpha.2.1" or "3.0.0.1".
* If we are working on a prerelease version, we append a prerelease tag:
"3.0.0.alpha.3.0.pre". The prerelease tag comes at the end so that when
version numbers are sorted, any prerelease builds are ordered between the
prior version and current version.
These rules are designed to work with the sorting rules for
[Gem::Version](http://ruby-doc.org/stdlib-2.0/libdoc/rubygems/rdoc/Gem/Version.html):
release numbers should sort in actual release order.

167
deps/protobuf/ruby/Rakefile vendored Normal file
View File

@ -0,0 +1,167 @@
require "rubygems"
require "rubygems/package_task"
require "rake/extensiontask" unless RUBY_PLATFORM == "java"
require "rake/testtask"
spec = Gem::Specification.load("google-protobuf.gemspec")
well_known_protos = %w[
google/protobuf/any.proto
google/protobuf/api.proto
google/protobuf/descriptor.proto
google/protobuf/duration.proto
google/protobuf/empty.proto
google/protobuf/field_mask.proto
google/protobuf/source_context.proto
google/protobuf/struct.proto
google/protobuf/timestamp.proto
google/protobuf/type.proto
google/protobuf/wrappers.proto
]
test_protos = %w[
tests/basic_test.proto
tests/basic_test_proto2.proto
tests/generated_code.proto
tests/generated_code_proto2.proto
tests/multi_level_nesting_test.proto
tests/test_import.proto
tests/test_import_proto2.proto
tests/test_ruby_package.proto
tests/test_ruby_package_proto2.proto
]
# These are omitted for now because we don't support proto2.
proto2_protos = %w[
google/protobuf/descriptor.proto
google/protobuf/compiler/plugin.proto
]
if system('../src/protoc --version')
protoc_command = '../src/protoc'
else
protoc_command = 'protoc'
end
genproto_output = []
# We won't have access to .. from within docker, but the proto files
# will be there, thanks to the :genproto rule dependency for gem:native.
unless ENV['IN_DOCKER'] == 'true'
well_known_protos.each do |proto_file|
input_file = "../src/" + proto_file
output_file = "lib/" + proto_file.sub(/\.proto$/, "_pb.rb")
genproto_output << output_file
file output_file => input_file do |file_task|
sh "#{protoc_command} -I../src --ruby_out=lib #{input_file}"
end
end
test_protos.each do |proto_file|
output_file = proto_file.sub(/\.proto$/, "_pb.rb")
genproto_output << output_file
file output_file => proto_file do |file_task|
sh "#{protoc_command} -I../src -I. -I./tests --ruby_out=. #{proto_file}"
end
end
end
if RUBY_PLATFORM == "java"
task :clean => :require_mvn do
system("mvn --batch-mode clean")
end
task :compile => :require_mvn do
system("mvn --batch-mode package")
end
task :require_mvn do
raise ArgumentError, "maven needs to be installed" if `which mvn` == ''
end
else
unless ENV['IN_DOCKER'] == 'true'
# We need utf8_range in-tree.
FileUtils.mkdir_p("ext/google/protobuf_c/third_party/utf8_range")
FileUtils.cp("../third_party/utf8_range/utf8_range.h", "ext/google/protobuf_c/third_party/utf8_range")
FileUtils.cp("../third_party/utf8_range/naive.c", "ext/google/protobuf_c/third_party/utf8_range")
FileUtils.cp("../third_party/utf8_range/range2-neon.c", "ext/google/protobuf_c/third_party/utf8_range")
FileUtils.cp("../third_party/utf8_range/range2-sse.c", "ext/google/protobuf_c/third_party/utf8_range")
FileUtils.cp("../third_party/utf8_range/LICENSE", "ext/google/protobuf_c/third_party/utf8_range")
end
Rake::ExtensionTask.new("protobuf_c", spec) do |ext|
unless RUBY_PLATFORM =~ /darwin/
# TODO: also set "no_native to true" for mac if possible. As is,
# "no_native" can only be set if the RUBY_PLATFORM doing
# cross-compilation is contained in the "ext.cross_platform" array.
ext.no_native = true
end
ext.ext_dir = "ext/google/protobuf_c"
ext.lib_dir = "lib/google"
ext.cross_compile = true
ext.cross_platform = [
'x86-mingw32', 'x64-mingw32',
'x86_64-linux', 'x86-linux',
'x86_64-darwin', 'arm64-darwin',
]
end
task 'gem:java' do
sh "rm Gemfile.lock"
require 'rake_compiler_dock'
# Specify the repo root as the working and mount directory to provide access
# to the java directory
repo_root = File.realdirpath File.join(Dir.pwd, '..')
RakeCompilerDock.sh <<-"EOT", platform: 'jruby', rubyvm: :jruby, mountdir: repo_root, workdir: repo_root
sudo apt-get install maven -y && \
cd java && mvn install -Dmaven.test.skip=true && cd ../ruby && \
bundle && \
IN_DOCKER=true rake compile gem
EOT
end
task 'gem:windows' do
sh "rm Gemfile.lock"
require 'rake_compiler_dock'
['x86-mingw32', 'x64-mingw32', 'x86_64-linux', 'x86-linux'].each do |plat|
RakeCompilerDock.sh <<-"EOT", platform: plat
bundle && \
IN_DOCKER=true rake native:#{plat} pkg/#{spec.full_name}-#{plat}.gem RUBY_CC_VERSION=3.1.0:3.0.0:2.7.0:2.6.0:2.5.0
EOT
end
end
if RUBY_PLATFORM =~ /darwin/
task 'gem:native' do
system "rake genproto"
system "rake cross native gem RUBY_CC_VERSION=3.1.0:3.0.0:2.7.0:2.6.0:2.5.1"
end
else
task 'gem:native' => [:genproto, 'gem:windows', 'gem:java']
end
end
task :genproto => genproto_output
task :clean do
sh "rm -f #{genproto_output.join(' ')}"
end
Gem::PackageTask.new(spec) do |pkg|
end
Rake::TestTask.new(:test => [:build, :genproto]) do |t|
t.test_files = FileList["tests/*.rb"].exclude("tests/gc_test.rb", "tests/common_tests.rb")
end
# gc_test needs to be split out to ensure the generated file hasn't been
# imported by other tests.
Rake::TestTask.new(:gc_test => :build) do |t|
t.test_files = FileList["tests/gc_test.rb"]
end
task :build => [:clean, :genproto, :compile]
task :default => [:build]
# vim:sw=2:et

View File

@ -0,0 +1,5 @@
# Protobuf Ruby Compatibility Tests
This directory contains a snapshot of protobuf ruby 3.0.0 unittest code and
test scripts used to verifies whether the latest version of protobuf is
still compatible with 3.0.0 generated code.

View File

@ -0,0 +1,25 @@
require "rake/testtask"
# Proto for tests.
genproto_output = []
genproto_output << "tests/generated_code.rb"
genproto_output << "tests/test_import.rb"
file "tests/generated_code.rb" => "tests/generated_code.proto" do |file_task|
sh "./protoc --ruby_out=. tests/generated_code.proto"
end
file "tests/test_import.rb" => "tests/test_import.proto" do |file_task|
sh "./protoc --ruby_out=. tests/test_import.proto"
end
task :genproto => genproto_output
task :clean do
sh "rm -f #{genproto_output.join(' ')}"
end
Rake::TestTask.new(:test => :genproto) do |t|
t.test_files = FileList["tests/*.rb"]
end
task :default => [:test]

View File

@ -0,0 +1,17 @@
#!/bin/bash
set -ex
# Change to the script's directory
cd $(dirname $0)
# Download 3.0.0 version of protoc
PROTOC_BINARY_NAME="protoc-3.0.0-linux-x86_64.exe"
if [ `uname` = "Darwin" ]; then
PROTOC_BINARY_NAME="protoc-3.0.0-osx-x86_64.exe"
fi
wget https://repo1.maven.org/maven2/com/google/protobuf/protoc/3.0.0/${PROTOC_BINARY_NAME} -O protoc
chmod +x protoc
# Run tests
RUBYLIB=../../lib:. rake test

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,67 @@
syntax = "proto3";
package a.b.c;
message TestMessage {
int32 optional_int32 = 1;
int64 optional_int64 = 2;
uint32 optional_uint32 = 3;
uint64 optional_uint64 = 4;
bool optional_bool = 5;
double optional_double = 6;
float optional_float = 7;
string optional_string = 8;
bytes optional_bytes = 9;
TestEnum optional_enum = 10;
TestMessage optional_msg = 11;
repeated int32 repeated_int32 = 21;
repeated int64 repeated_int64 = 22;
repeated uint32 repeated_uint32 = 23;
repeated uint64 repeated_uint64 = 24;
repeated bool repeated_bool = 25;
repeated double repeated_double = 26;
repeated float repeated_float = 27;
repeated string repeated_string = 28;
repeated bytes repeated_bytes = 29;
repeated TestEnum repeated_enum = 30;
repeated TestMessage repeated_msg = 31;
oneof my_oneof {
int32 oneof_int32 = 41;
int64 oneof_int64 = 42;
uint32 oneof_uint32 = 43;
uint64 oneof_uint64 = 44;
bool oneof_bool = 45;
double oneof_double = 46;
float oneof_float = 47;
string oneof_string = 48;
bytes oneof_bytes = 49;
TestEnum oneof_enum = 50;
TestMessage oneof_msg = 51;
}
map<int32, string> map_int32_string = 61;
map<int64, string> map_int64_string = 62;
map<uint32, string> map_uint32_string = 63;
map<uint64, string> map_uint64_string = 64;
map<bool, string> map_bool_string = 65;
map<string, string> map_string_string = 66;
map<string, TestMessage> map_string_msg = 67;
map<string, TestEnum> map_string_enum = 68;
map<string, int32> map_string_int32 = 69;
map<string, bool> map_string_bool = 70;
message NestedMessage {
int32 foo = 1;
}
NestedMessage nested_message = 80;
}
enum TestEnum {
Default = 0;
A = 1;
B = 2;
C = 3;
}

View File

@ -0,0 +1,19 @@
#!/usr/bin/ruby
# generated_code.rb is in the same directory as this test.
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
require 'generated_code_pb'
require 'test_import_pb'
require 'test/unit'
class GeneratedCodeTest < Test::Unit::TestCase
def test_generated_msg
# just test that we can instantiate the message. The purpose of this test
# is to ensure that the output of the code generator is valid Ruby and
# successfully creates message definitions and classes, not to test every
# aspect of the extension (basic.rb is for that).
m = A::B::C::TestMessage.new()
m2 = FooBar::TestImportedMessage.new()
end
end

View File

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

View File

@ -0,0 +1,38 @@
#!/usr/bin/ruby
require 'google/protobuf'
require 'test/unit'
module StressTest
pool = Google::Protobuf::DescriptorPool.new
pool.build do
add_message "TestMessage" do
optional :a, :int32, 1
repeated :b, :message, 2, "M"
end
add_message "M" do
optional :foo, :string, 1
end
end
TestMessage = pool.lookup("TestMessage").msgclass
M = pool.lookup("M").msgclass
class StressTest < Test::Unit::TestCase
def get_msg
TestMessage.new(:a => 1000,
:b => [M.new(:foo => "hello"),
M.new(:foo => "world")])
end
def test_stress
m = get_msg
data = TestMessage.encode(m)
100_000.times do
mnew = TestMessage.decode(data)
mnew = mnew.dup
assert_equal mnew.inspect, m.inspect
assert TestMessage.encode(mnew) == data
end
end
end
end

View File

@ -0,0 +1,5 @@
syntax = "proto3";
package foo_bar;
message TestImportedMessage {}

View File

@ -0,0 +1,361 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// 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.
// -----------------------------------------------------------------------------
// Ruby <-> upb data conversion functions.
//
// This file Also contains a few other assorted algorithms on upb_MessageValue.
//
// None of the algorithms in this file require any access to the internal
// representation of Ruby or upb objects.
// -----------------------------------------------------------------------------
#include "convert.h"
#include "message.h"
#include "protobuf.h"
static upb_StringView Convert_StringData(VALUE str, upb_Arena* arena) {
upb_StringView ret;
if (arena) {
char* ptr = upb_Arena_Malloc(arena, RSTRING_LEN(str));
memcpy(ptr, RSTRING_PTR(str), RSTRING_LEN(str));
ret.data = ptr;
} else {
// Data is only needed temporarily (within map lookup).
ret.data = RSTRING_PTR(str);
}
ret.size = RSTRING_LEN(str);
return ret;
}
static bool is_ruby_num(VALUE value) {
return (TYPE(value) == T_FLOAT || TYPE(value) == T_FIXNUM ||
TYPE(value) == T_BIGNUM);
}
static void Convert_CheckInt(const char* name, upb_CType type, VALUE val) {
if (!is_ruby_num(val)) {
rb_raise(cTypeError,
"Expected number type for integral field '%s' (given %s).", name,
rb_class2name(CLASS_OF(val)));
}
// NUM2{INT,UINT,LL,ULL} macros do the appropriate range checks on upper
// bound; we just need to do precision checks (i.e., disallow rounding) and
// check for < 0 on unsigned types.
if (TYPE(val) == T_FLOAT) {
double dbl_val = NUM2DBL(val);
if (floor(dbl_val) != dbl_val) {
rb_raise(rb_eRangeError,
"Non-integral floating point value assigned to integer field "
"'%s' (given %s).",
name, rb_class2name(CLASS_OF(val)));
}
}
if (type == kUpb_CType_UInt32 || type == kUpb_CType_UInt64) {
if (NUM2DBL(val) < 0) {
rb_raise(
rb_eRangeError,
"Assigning negative value to unsigned integer field '%s' (given %s).",
name, rb_class2name(CLASS_OF(val)));
}
}
}
static int32_t Convert_ToEnum(VALUE value, const char* name,
const upb_EnumDef* e) {
int32_t val;
switch (TYPE(value)) {
case T_FLOAT:
case T_FIXNUM:
case T_BIGNUM:
Convert_CheckInt(name, kUpb_CType_Int32, value);
val = NUM2INT(value);
break;
case T_STRING: {
const upb_EnumValueDef* ev = upb_EnumDef_FindValueByNameWithSize(
e, RSTRING_PTR(value), RSTRING_LEN(value));
if (!ev) goto unknownval;
val = upb_EnumValueDef_Number(ev);
break;
}
case T_SYMBOL: {
const upb_EnumValueDef* ev =
upb_EnumDef_FindValueByName(e, rb_id2name(SYM2ID(value)));
if (!ev)
goto unknownval;
val = upb_EnumValueDef_Number(ev);
break;
}
default:
rb_raise(cTypeError,
"Expected number or symbol type for enum field '%s'.", name);
}
return val;
unknownval:
rb_raise(rb_eRangeError, "Unknown symbol value for enum field '%s'.", name);
}
upb_MessageValue Convert_RubyToUpb(VALUE value, const char* name,
TypeInfo type_info, upb_Arena* arena) {
upb_MessageValue ret;
switch (type_info.type) {
case kUpb_CType_Float:
if (!is_ruby_num(value)) {
rb_raise(cTypeError,
"Expected number type for float field '%s' (given %s).", name,
rb_class2name(CLASS_OF(value)));
}
ret.float_val = NUM2DBL(value);
break;
case kUpb_CType_Double:
if (!is_ruby_num(value)) {
rb_raise(cTypeError,
"Expected number type for double field '%s' (given %s).", name,
rb_class2name(CLASS_OF(value)));
}
ret.double_val = NUM2DBL(value);
break;
case kUpb_CType_Bool: {
if (value == Qtrue) {
ret.bool_val = 1;
} else if (value == Qfalse) {
ret.bool_val = 0;
} else {
rb_raise(cTypeError,
"Invalid argument for boolean field '%s' (given %s).", name,
rb_class2name(CLASS_OF(value)));
}
break;
}
case kUpb_CType_String: {
VALUE utf8 = rb_enc_from_encoding(rb_utf8_encoding());
if (rb_obj_class(value) == rb_cSymbol) {
value = rb_funcall(value, rb_intern("to_s"), 0);
} else if (rb_obj_class(value) != rb_cString) {
rb_raise(cTypeError,
"Invalid argument for string field '%s' (given %s).", name,
rb_class2name(CLASS_OF(value)));
}
if (rb_obj_encoding(value) != utf8) {
// Note: this will not duplicate underlying string data unless
// necessary.
value = rb_str_encode(value, utf8, 0, Qnil);
if (rb_enc_str_coderange(value) == ENC_CODERANGE_BROKEN) {
rb_raise(rb_eEncodingError, "String is invalid UTF-8");
}
}
ret.str_val = Convert_StringData(value, arena);
break;
}
case kUpb_CType_Bytes: {
VALUE bytes = rb_enc_from_encoding(rb_ascii8bit_encoding());
if (rb_obj_class(value) != rb_cString) {
rb_raise(cTypeError,
"Invalid argument for bytes field '%s' (given %s).", name,
rb_class2name(CLASS_OF(value)));
}
if (rb_obj_encoding(value) != bytes) {
// Note: this will not duplicate underlying string data unless
// necessary.
// TODO(haberman): is this really necessary to get raw bytes?
value = rb_str_encode(value, bytes, 0, Qnil);
}
ret.str_val = Convert_StringData(value, arena);
break;
}
case kUpb_CType_Message:
ret.msg_val =
Message_GetUpbMessage(value, type_info.def.msgdef, name, arena);
break;
case kUpb_CType_Enum:
ret.int32_val = Convert_ToEnum(value, name, type_info.def.enumdef);
break;
case kUpb_CType_Int32:
case kUpb_CType_Int64:
case kUpb_CType_UInt32:
case kUpb_CType_UInt64:
Convert_CheckInt(name, type_info.type, value);
switch (type_info.type) {
case kUpb_CType_Int32:
ret.int32_val = NUM2INT(value);
break;
case kUpb_CType_Int64:
ret.int64_val = NUM2LL(value);
break;
case kUpb_CType_UInt32:
ret.uint32_val = NUM2UINT(value);
break;
case kUpb_CType_UInt64:
ret.uint64_val = NUM2ULL(value);
break;
default:
break;
}
break;
default:
break;
}
return ret;
}
VALUE Convert_UpbToRuby(upb_MessageValue upb_val, TypeInfo type_info,
VALUE arena) {
switch (type_info.type) {
case kUpb_CType_Float:
return DBL2NUM(upb_val.float_val);
case kUpb_CType_Double:
return DBL2NUM(upb_val.double_val);
case kUpb_CType_Bool:
return upb_val.bool_val ? Qtrue : Qfalse;
case kUpb_CType_Int32:
return INT2NUM(upb_val.int32_val);
case kUpb_CType_Int64:
return LL2NUM(upb_val.int64_val);
case kUpb_CType_UInt32:
return UINT2NUM(upb_val.uint32_val);
case kUpb_CType_UInt64:
return ULL2NUM(upb_val.int64_val);
case kUpb_CType_Enum: {
const upb_EnumValueDef *ev = upb_EnumDef_FindValueByNumber(
type_info.def.enumdef, upb_val.int32_val);
if (ev) {
return ID2SYM(rb_intern(upb_EnumValueDef_Name(ev)));
} else {
return INT2NUM(upb_val.int32_val);
}
}
case kUpb_CType_String: {
VALUE str_rb = rb_str_new(upb_val.str_val.data, upb_val.str_val.size);
rb_enc_associate(str_rb, rb_utf8_encoding());
rb_obj_freeze(str_rb);
return str_rb;
}
case kUpb_CType_Bytes: {
VALUE str_rb = rb_str_new(upb_val.str_val.data, upb_val.str_val.size);
rb_enc_associate(str_rb, rb_ascii8bit_encoding());
rb_obj_freeze(str_rb);
return str_rb;
}
case kUpb_CType_Message:
return Message_GetRubyWrapper((upb_Message*)upb_val.msg_val,
type_info.def.msgdef, arena);
default:
rb_raise(rb_eRuntimeError, "Convert_UpbToRuby(): Unexpected type %d",
(int)type_info.type);
}
}
upb_MessageValue Msgval_DeepCopy(upb_MessageValue msgval, TypeInfo type_info,
upb_Arena* arena) {
upb_MessageValue new_msgval;
switch (type_info.type) {
default:
memcpy(&new_msgval, &msgval, sizeof(msgval));
break;
case kUpb_CType_String:
case kUpb_CType_Bytes: {
size_t n = msgval.str_val.size;
char* mem = upb_Arena_Malloc(arena, n);
new_msgval.str_val.data = mem;
new_msgval.str_val.size = n;
memcpy(mem, msgval.str_val.data, n);
break;
}
case kUpb_CType_Message:
new_msgval.msg_val =
Message_deep_copy(msgval.msg_val, type_info.def.msgdef, arena);
break;
}
return new_msgval;
}
bool Msgval_IsEqual(upb_MessageValue val1, upb_MessageValue val2,
TypeInfo type_info) {
switch (type_info.type) {
case kUpb_CType_Bool:
return memcmp(&val1, &val2, 1) == 0;
case kUpb_CType_Float:
case kUpb_CType_Int32:
case kUpb_CType_UInt32:
case kUpb_CType_Enum:
return memcmp(&val1, &val2, 4) == 0;
case kUpb_CType_Double:
case kUpb_CType_Int64:
case kUpb_CType_UInt64:
return memcmp(&val1, &val2, 8) == 0;
case kUpb_CType_String:
case kUpb_CType_Bytes:
return val1.str_val.size == val2.str_val.size &&
memcmp(val1.str_val.data, val2.str_val.data, val1.str_val.size) ==
0;
case kUpb_CType_Message:
return Message_Equal(val1.msg_val, val2.msg_val, type_info.def.msgdef);
default:
rb_raise(rb_eRuntimeError, "Internal error, unexpected type");
}
}
uint64_t Msgval_GetHash(upb_MessageValue val, TypeInfo type_info,
uint64_t seed) {
switch (type_info.type) {
case kUpb_CType_Bool:
return _upb_Hash(&val, 1, seed);
case kUpb_CType_Float:
case kUpb_CType_Int32:
case kUpb_CType_UInt32:
case kUpb_CType_Enum:
return _upb_Hash(&val, 4, seed);
case kUpb_CType_Double:
case kUpb_CType_Int64:
case kUpb_CType_UInt64:
return _upb_Hash(&val, 8, seed);
case kUpb_CType_String:
case kUpb_CType_Bytes:
return _upb_Hash(val.str_val.data, val.str_val.size, seed);
case kUpb_CType_Message:
return Message_Hash(val.msg_val, type_info.def.msgdef, seed);
default:
rb_raise(rb_eRuntimeError, "Internal error, unexpected type");
}
}

View File

@ -0,0 +1,75 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// 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 RUBY_PROTOBUF_CONVERT_H_
#define RUBY_PROTOBUF_CONVERT_H_
#include <ruby/ruby.h>
#include "protobuf.h"
#include "ruby-upb.h"
// Converts |ruby_val| to a upb_MessageValue according to |type_info|.
//
// The |arena| parameter indicates the lifetime of the container where this
// value will be assigned. It is used as follows:
// - If type is string or bytes, the string data will be copied into |arena|.
// - If type is message, and we need to auto-construct a message due to implicit
// conversions (eg. Time -> Google::Protobuf::Timestamp), the new message
// will be created in |arena|.
// - If type is message and the Ruby value is a message instance, we will fuse
// the message's arena into |arena|, to ensure that this message outlives the
// container.
upb_MessageValue Convert_RubyToUpb(VALUE ruby_val, const char *name,
TypeInfo type_info, upb_Arena *arena);
// Converts |upb_val| to a Ruby VALUE according to |type_info|. This may involve
// creating a Ruby wrapper object.
//
// The |arena| parameter indicates the arena that owns the lifetime of
// |upb_val|. Any Ruby wrapper object that is created will reference |arena|
// and ensure it outlives the wrapper.
VALUE Convert_UpbToRuby(upb_MessageValue upb_val, TypeInfo type_info,
VALUE arena);
// Creates a deep copy of |msgval| in |arena|.
upb_MessageValue Msgval_DeepCopy(upb_MessageValue msgval, TypeInfo type_info,
upb_Arena *arena);
// Returns true if |val1| and |val2| are equal. Their type is given by
// |type_info|.
bool Msgval_IsEqual(upb_MessageValue val1, upb_MessageValue val2,
TypeInfo type_info);
// Returns a hash value for the given upb_MessageValue.
uint64_t Msgval_GetHash(upb_MessageValue val, TypeInfo type_info,
uint64_t seed);
#endif // RUBY_PROTOBUF_CONVERT_H_

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,107 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// 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 RUBY_PROTOBUF_DEFS_H_
#define RUBY_PROTOBUF_DEFS_H_
#include <ruby/ruby.h>
#include "protobuf.h"
#include "ruby-upb.h"
// -----------------------------------------------------------------------------
// TypeInfo
// -----------------------------------------------------------------------------
// This bundles a upb_CType and msgdef/enumdef when appropriate. This is
// convenient for functions that need type information but cannot necessarily
// assume a upb_FieldDef will be available.
//
// For example, Google::Protobuf::Map and Google::Protobuf::RepeatedField can
// be constructed with type information alone:
//
// # RepeatedField will internally store the type information in a TypeInfo.
// Google::Protobuf::RepeatedField.new(:message, FooMessage)
typedef struct {
upb_CType type;
union {
const upb_MessageDef* msgdef; // When type == kUpb_CType_Message
const upb_EnumDef* enumdef; // When type == kUpb_CType_Enum
} def;
} TypeInfo;
static inline TypeInfo TypeInfo_get(const upb_FieldDef* f) {
TypeInfo ret = {upb_FieldDef_CType(f), {NULL}};
switch (ret.type) {
case kUpb_CType_Message:
ret.def.msgdef = upb_FieldDef_MessageSubDef(f);
break;
case kUpb_CType_Enum:
ret.def.enumdef = upb_FieldDef_EnumSubDef(f);
break;
default:
break;
}
return ret;
}
TypeInfo TypeInfo_FromClass(int argc, VALUE* argv, int skip_arg,
VALUE* type_class, VALUE* init_arg);
static inline TypeInfo TypeInfo_from_type(upb_CType type) {
TypeInfo ret = {type};
assert(type != kUpb_CType_Message && type != kUpb_CType_Enum);
return ret;
}
// -----------------------------------------------------------------------------
// Other utilities
// -----------------------------------------------------------------------------
VALUE Descriptor_DefToClass(const upb_MessageDef* m);
// Returns the underlying msgdef, enumdef, or symtab (respectively) for the
// given Descriptor, EnumDescriptor, or DescriptorPool Ruby object.
const upb_EnumDef* EnumDescriptor_GetEnumDef(VALUE enum_desc_rb);
const upb_DefPool* DescriptorPool_GetSymtab(VALUE desc_pool_rb);
const upb_MessageDef* Descriptor_GetMsgDef(VALUE desc_rb);
// Returns a upb field type for the given Ruby symbol
// (eg. :float => kUpb_CType_Float).
upb_CType ruby_to_fieldtype(VALUE type);
// The singleton generated pool (a DescriptorPool object).
extern VALUE generated_pool;
// Call at startup to register all types in this module.
void Defs_register(VALUE module);
#endif // RUBY_PROTOBUF_DEFS_H_

View File

@ -0,0 +1,28 @@
#!/usr/bin/ruby
require 'mkmf'
ext_name = "google/protobuf_c"
dir_config(ext_name)
if RUBY_PLATFORM =~ /darwin/ || RUBY_PLATFORM =~ /linux/
$CFLAGS += " -std=gnu99 -O3 -DNDEBUG -fvisibility=hidden -Wall -Wsign-compare -Wno-declaration-after-statement"
else
$CFLAGS += " -std=gnu99 -O3 -DNDEBUG"
end
if RUBY_PLATFORM =~ /linux/
# Instruct the linker to point memcpy calls at our __wrap_memcpy wrapper.
$LDFLAGS += " -Wl,-wrap,memcpy"
end
$VPATH << "$(srcdir)/third_party/utf8_range"
$INCFLAGS << "$(srcdir)/third_party/utf8_range"
$srcs = ["protobuf.c", "convert.c", "defs.c", "message.c",
"repeated_field.c", "map.c", "ruby-upb.c", "wrap_memcpy.c",
"naive.c", "range2-neon.c", "range2-sse.c"]
create_makefile(ext_name)

View File

@ -0,0 +1,702 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2014 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// 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 "convert.h"
#include "defs.h"
#include "message.h"
#include "protobuf.h"
// -----------------------------------------------------------------------------
// Basic map operations on top of upb_Map.
//
// Note that we roll our own `Map` container here because, as for
// `RepeatedField`, we want a strongly-typed container. This is so that any user
// errors due to incorrect map key or value types are raised as close as
// possible to the error site, rather than at some deferred point (e.g.,
// serialization).
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// Map container type.
// -----------------------------------------------------------------------------
typedef struct {
const upb_Map* map; // Can convert to mutable when non-frozen.
upb_CType key_type;
TypeInfo value_type_info;
VALUE value_type_class;
VALUE arena;
} Map;
static void Map_mark(void* _self) {
Map* self = _self;
rb_gc_mark(self->value_type_class);
rb_gc_mark(self->arena);
}
const rb_data_type_t Map_type = {
"Google::Protobuf::Map",
{Map_mark, RUBY_DEFAULT_FREE, NULL},
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
};
VALUE cMap;
static Map* ruby_to_Map(VALUE _self) {
Map* self;
TypedData_Get_Struct(_self, Map, &Map_type, self);
return self;
}
static VALUE Map_alloc(VALUE klass) {
Map* self = ALLOC(Map);
self->map = NULL;
self->value_type_class = Qnil;
self->value_type_info.def.msgdef = NULL;
self->arena = Qnil;
return TypedData_Wrap_Struct(klass, &Map_type, self);
}
VALUE Map_GetRubyWrapper(upb_Map* map, upb_CType key_type, TypeInfo value_type,
VALUE arena) {
PBRUBY_ASSERT(map);
VALUE val = ObjectCache_Get(map);
if (val == Qnil) {
val = Map_alloc(cMap);
Map* self;
ObjectCache_Add(map, val);
TypedData_Get_Struct(val, Map, &Map_type, self);
self->map = map;
self->arena = arena;
self->key_type = key_type;
self->value_type_info = value_type;
if (self->value_type_info.type == kUpb_CType_Message) {
const upb_MessageDef* val_m = self->value_type_info.def.msgdef;
self->value_type_class = Descriptor_DefToClass(val_m);
}
}
return val;
}
static VALUE Map_new_this_type(Map* from) {
VALUE arena_rb = Arena_new();
upb_Map* map = upb_Map_New(Arena_get(arena_rb), from->key_type,
from->value_type_info.type);
VALUE ret =
Map_GetRubyWrapper(map, from->key_type, from->value_type_info, arena_rb);
PBRUBY_ASSERT(ruby_to_Map(ret)->value_type_class == from->value_type_class);
return ret;
}
static TypeInfo Map_keyinfo(Map* self) {
TypeInfo ret;
ret.type = self->key_type;
ret.def.msgdef = NULL;
return ret;
}
static upb_Map* Map_GetMutable(VALUE _self) {
rb_check_frozen(_self);
return (upb_Map*)ruby_to_Map(_self)->map;
}
VALUE Map_CreateHash(const upb_Map* map, upb_CType key_type,
TypeInfo val_info) {
VALUE hash = rb_hash_new();
size_t iter = kUpb_Map_Begin;
TypeInfo key_info = TypeInfo_from_type(key_type);
if (!map) return hash;
while (upb_MapIterator_Next(map, &iter)) {
upb_MessageValue key = upb_MapIterator_Key(map, iter);
upb_MessageValue val = upb_MapIterator_Value(map, iter);
VALUE key_val = Convert_UpbToRuby(key, key_info, Qnil);
VALUE val_val = Scalar_CreateHash(val, val_info);
rb_hash_aset(hash, key_val, val_val);
}
return hash;
}
VALUE Map_deep_copy(VALUE obj) {
Map* self = ruby_to_Map(obj);
VALUE new_arena_rb = Arena_new();
upb_Arena* arena = Arena_get(new_arena_rb);
upb_Map* new_map =
upb_Map_New(arena, self->key_type, self->value_type_info.type);
size_t iter = kUpb_Map_Begin;
while (upb_MapIterator_Next(self->map, &iter)) {
upb_MessageValue key = upb_MapIterator_Key(self->map, iter);
upb_MessageValue val = upb_MapIterator_Value(self->map, iter);
upb_MessageValue val_copy =
Msgval_DeepCopy(val, self->value_type_info, arena);
upb_Map_Set(new_map, key, val_copy, arena);
}
return Map_GetRubyWrapper(new_map, self->key_type, self->value_type_info,
new_arena_rb);
}
const upb_Map* Map_GetUpbMap(VALUE val, const upb_FieldDef* field,
upb_Arena* arena) {
const upb_FieldDef* key_field = map_field_key(field);
const upb_FieldDef* value_field = map_field_value(field);
TypeInfo value_type_info = TypeInfo_get(value_field);
Map* self;
if (!RB_TYPE_P(val, T_DATA) || !RTYPEDDATA_P(val) ||
RTYPEDDATA_TYPE(val) != &Map_type) {
rb_raise(cTypeError, "Expected Map instance");
}
self = ruby_to_Map(val);
if (self->key_type != upb_FieldDef_CType(key_field)) {
rb_raise(cTypeError, "Map key type does not match field's key type");
}
if (self->value_type_info.type != value_type_info.type) {
rb_raise(cTypeError, "Map value type does not match field's value type");
}
if (self->value_type_info.def.msgdef != value_type_info.def.msgdef) {
rb_raise(cTypeError, "Map value type has wrong message/enum class");
}
Arena_fuse(self->arena, arena);
return self->map;
}
void Map_Inspect(StringBuilder* b, const upb_Map* map, upb_CType key_type,
TypeInfo val_type) {
bool first = true;
TypeInfo key_type_info = {key_type};
StringBuilder_Printf(b, "{");
if (map) {
size_t iter = kUpb_Map_Begin;
while (upb_MapIterator_Next(map, &iter)) {
upb_MessageValue key = upb_MapIterator_Key(map, iter);
upb_MessageValue val = upb_MapIterator_Value(map, iter);
if (first) {
first = false;
} else {
StringBuilder_Printf(b, ", ");
}
StringBuilder_PrintMsgval(b, key, key_type_info);
StringBuilder_Printf(b, "=>");
StringBuilder_PrintMsgval(b, val, val_type);
}
}
StringBuilder_Printf(b, "}");
}
static int merge_into_self_callback(VALUE key, VALUE val, VALUE _self) {
Map* self = ruby_to_Map(_self);
upb_Arena* arena = Arena_get(self->arena);
upb_MessageValue key_val =
Convert_RubyToUpb(key, "", Map_keyinfo(self), arena);
upb_MessageValue val_val =
Convert_RubyToUpb(val, "", self->value_type_info, arena);
upb_Map_Set(Map_GetMutable(_self), key_val, val_val, arena);
return ST_CONTINUE;
}
// Used only internally -- shared by #merge and #initialize.
static VALUE Map_merge_into_self(VALUE _self, VALUE hashmap) {
if (TYPE(hashmap) == T_HASH) {
rb_hash_foreach(hashmap, merge_into_self_callback, _self);
} else if (RB_TYPE_P(hashmap, T_DATA) && RTYPEDDATA_P(hashmap) &&
RTYPEDDATA_TYPE(hashmap) == &Map_type) {
Map* self = ruby_to_Map(_self);
Map* other = ruby_to_Map(hashmap);
upb_Arena* arena = Arena_get(self->arena);
upb_Message* self_msg = Map_GetMutable(_self);
size_t iter = kUpb_Map_Begin;
Arena_fuse(other->arena, arena);
if (self->key_type != other->key_type ||
self->value_type_info.type != other->value_type_info.type ||
self->value_type_class != other->value_type_class) {
rb_raise(rb_eArgError, "Attempt to merge Map with mismatching types");
}
while (upb_MapIterator_Next(other->map, &iter)) {
upb_MessageValue key = upb_MapIterator_Key(other->map, iter);
upb_MessageValue val = upb_MapIterator_Value(other->map, iter);
upb_Map_Set(self_msg, key, val, arena);
}
} else {
rb_raise(rb_eArgError, "Unknown type merging into Map");
}
return _self;
}
/*
* call-seq:
* Map.new(key_type, value_type, value_typeclass = nil, init_hashmap = {})
* => new map
*
* Allocates a new Map container. This constructor may be called with 2, 3, or 4
* arguments. The first two arguments are always present and are symbols (taking
* on the same values as field-type symbols in message descriptors) that
* indicate the type of the map key and value fields.
*
* The supported key types are: :int32, :int64, :uint32, :uint64, :bool,
* :string, :bytes.
*
* The supported value types are: :int32, :int64, :uint32, :uint64, :bool,
* :string, :bytes, :enum, :message.
*
* The third argument, value_typeclass, must be present if value_type is :enum
* or :message. As in RepeatedField#new, this argument must be a message class
* (for :message) or enum module (for :enum).
*
* The last argument, if present, provides initial content for map. Note that
* this may be an ordinary Ruby hashmap or another Map instance with identical
* key and value types. Also note that this argument may be present whether or
* not value_typeclass is present (and it is unambiguously separate from
* value_typeclass because value_typeclass's presence is strictly determined by
* value_type). The contents of this initial hashmap or Map instance are
* shallow-copied into the new Map: the original map is unmodified, but
* references to underlying objects will be shared if the value type is a
* message type.
*/
static VALUE Map_init(int argc, VALUE* argv, VALUE _self) {
Map* self = ruby_to_Map(_self);
VALUE init_arg;
// We take either two args (:key_type, :value_type), three args (:key_type,
// :value_type, "ValueMessageType"), or four args (the above plus an initial
// hashmap).
if (argc < 2 || argc > 4) {
rb_raise(rb_eArgError, "Map constructor expects 2, 3 or 4 arguments.");
}
self->key_type = ruby_to_fieldtype(argv[0]);
self->value_type_info =
TypeInfo_FromClass(argc, argv, 1, &self->value_type_class, &init_arg);
self->arena = Arena_new();
// Check that the key type is an allowed type.
switch (self->key_type) {
case kUpb_CType_Int32:
case kUpb_CType_Int64:
case kUpb_CType_UInt32:
case kUpb_CType_UInt64:
case kUpb_CType_Bool:
case kUpb_CType_String:
case kUpb_CType_Bytes:
// These are OK.
break;
default:
rb_raise(rb_eArgError, "Invalid key type for map.");
}
self->map = upb_Map_New(Arena_get(self->arena), self->key_type,
self->value_type_info.type);
ObjectCache_Add(self->map, _self);
if (init_arg != Qnil) {
Map_merge_into_self(_self, init_arg);
}
return Qnil;
}
/*
* call-seq:
* Map.each(&block)
*
* Invokes &block on each |key, value| pair in the map, in unspecified order.
* Note that Map also includes Enumerable; map thus acts like a normal Ruby
* sequence.
*/
static VALUE Map_each(VALUE _self) {
Map* self = ruby_to_Map(_self);
size_t iter = kUpb_Map_Begin;
while (upb_MapIterator_Next(self->map, &iter)) {
upb_MessageValue key = upb_MapIterator_Key(self->map, iter);
upb_MessageValue val = upb_MapIterator_Value(self->map, iter);
VALUE key_val = Convert_UpbToRuby(key, Map_keyinfo(self), self->arena);
VALUE val_val = Convert_UpbToRuby(val, self->value_type_info, self->arena);
rb_yield_values(2, key_val, val_val);
}
return Qnil;
}
/*
* call-seq:
* Map.keys => [list_of_keys]
*
* Returns the list of keys contained in the map, in unspecified order.
*/
static VALUE Map_keys(VALUE _self) {
Map* self = ruby_to_Map(_self);
size_t iter = kUpb_Map_Begin;
VALUE ret = rb_ary_new();
while (upb_MapIterator_Next(self->map, &iter)) {
upb_MessageValue key = upb_MapIterator_Key(self->map, iter);
VALUE key_val = Convert_UpbToRuby(key, Map_keyinfo(self), self->arena);
rb_ary_push(ret, key_val);
}
return ret;
}
/*
* call-seq:
* Map.values => [list_of_values]
*
* Returns the list of values contained in the map, in unspecified order.
*/
static VALUE Map_values(VALUE _self) {
Map* self = ruby_to_Map(_self);
size_t iter = kUpb_Map_Begin;
VALUE ret = rb_ary_new();
while (upb_MapIterator_Next(self->map, &iter)) {
upb_MessageValue val = upb_MapIterator_Value(self->map, iter);
VALUE val_val = Convert_UpbToRuby(val, self->value_type_info, self->arena);
rb_ary_push(ret, val_val);
}
return ret;
}
/*
* call-seq:
* Map.[](key) => value
*
* Accesses the element at the given key. Throws an exception if the key type is
* incorrect. Returns nil when the key is not present in the map.
*/
static VALUE Map_index(VALUE _self, VALUE key) {
Map* self = ruby_to_Map(_self);
upb_MessageValue key_upb =
Convert_RubyToUpb(key, "", Map_keyinfo(self), NULL);
upb_MessageValue val;
if (upb_Map_Get(self->map, key_upb, &val)) {
return Convert_UpbToRuby(val, self->value_type_info, self->arena);
} else {
return Qnil;
}
}
/*
* call-seq:
* Map.[]=(key, value) => value
*
* Inserts or overwrites the value at the given key with the given new value.
* Throws an exception if the key type is incorrect. Returns the new value that
* was just inserted.
*/
static VALUE Map_index_set(VALUE _self, VALUE key, VALUE val) {
Map* self = ruby_to_Map(_self);
upb_Arena* arena = Arena_get(self->arena);
upb_MessageValue key_upb =
Convert_RubyToUpb(key, "", Map_keyinfo(self), NULL);
upb_MessageValue val_upb =
Convert_RubyToUpb(val, "", self->value_type_info, arena);
upb_Map_Set(Map_GetMutable(_self), key_upb, val_upb, arena);
return val;
}
/*
* call-seq:
* Map.has_key?(key) => bool
*
* Returns true if the given key is present in the map. Throws an exception if
* the key has the wrong type.
*/
static VALUE Map_has_key(VALUE _self, VALUE key) {
Map* self = ruby_to_Map(_self);
upb_MessageValue key_upb =
Convert_RubyToUpb(key, "", Map_keyinfo(self), NULL);
if (upb_Map_Get(self->map, key_upb, NULL)) {
return Qtrue;
} else {
return Qfalse;
}
}
/*
* call-seq:
* Map.delete(key) => old_value
*
* Deletes the value at the given key, if any, returning either the old value or
* nil if none was present. Throws an exception if the key is of the wrong type.
*/
static VALUE Map_delete(VALUE _self, VALUE key) {
Map* self = ruby_to_Map(_self);
upb_MessageValue key_upb =
Convert_RubyToUpb(key, "", Map_keyinfo(self), NULL);
upb_MessageValue val_upb;
VALUE ret;
rb_check_frozen(_self);
// TODO(haberman): make upb_Map_Delete() also capable of returning the deleted
// value.
if (upb_Map_Get(self->map, key_upb, &val_upb)) {
ret = Convert_UpbToRuby(val_upb, self->value_type_info, self->arena);
} else {
ret = Qnil;
}
upb_Map_Delete(Map_GetMutable(_self), key_upb);
return ret;
}
/*
* call-seq:
* Map.clear
*
* Removes all entries from the map.
*/
static VALUE Map_clear(VALUE _self) {
upb_Map_Clear(Map_GetMutable(_self));
return Qnil;
}
/*
* call-seq:
* Map.length
*
* Returns the number of entries (key-value pairs) in the map.
*/
static VALUE Map_length(VALUE _self) {
Map* self = ruby_to_Map(_self);
return ULL2NUM(upb_Map_Size(self->map));
}
/*
* call-seq:
* Map.dup => new_map
*
* Duplicates this map with a shallow copy. References to all non-primitive
* element objects (e.g., submessages) are shared.
*/
static VALUE Map_dup(VALUE _self) {
Map* self = ruby_to_Map(_self);
VALUE new_map_rb = Map_new_this_type(self);
Map* new_self = ruby_to_Map(new_map_rb);
size_t iter = kUpb_Map_Begin;
upb_Arena* arena = Arena_get(new_self->arena);
upb_Map* new_map = Map_GetMutable(new_map_rb);
Arena_fuse(self->arena, arena);
while (upb_MapIterator_Next(self->map, &iter)) {
upb_MessageValue key = upb_MapIterator_Key(self->map, iter);
upb_MessageValue val = upb_MapIterator_Value(self->map, iter);
upb_Map_Set(new_map, key, val, arena);
}
return new_map_rb;
}
/*
* call-seq:
* Map.==(other) => boolean
*
* Compares this map to another. Maps are equal if they have identical key sets,
* and for each key, the values in both maps compare equal. Elements are
* compared as per normal Ruby semantics, by calling their :== methods (or
* performing a more efficient comparison for primitive types).
*
* Maps with dissimilar key types or value types/typeclasses are never equal,
* even if value comparison (for example, between integers and floats) would
* have otherwise indicated that every element has equal value.
*/
VALUE Map_eq(VALUE _self, VALUE _other) {
Map* self = ruby_to_Map(_self);
Map* other;
// Allow comparisons to Ruby hashmaps by converting to a temporary Map
// instance. Slow, but workable.
if (TYPE(_other) == T_HASH) {
VALUE other_map = Map_new_this_type(self);
Map_merge_into_self(other_map, _other);
_other = other_map;
}
other = ruby_to_Map(_other);
if (self == other) {
return Qtrue;
}
if (self->key_type != other->key_type ||
self->value_type_info.type != other->value_type_info.type ||
self->value_type_class != other->value_type_class) {
return Qfalse;
}
if (upb_Map_Size(self->map) != upb_Map_Size(other->map)) {
return Qfalse;
}
// For each member of self, check that an equal member exists at the same key
// in other.
size_t iter = kUpb_Map_Begin;
while (upb_MapIterator_Next(self->map, &iter)) {
upb_MessageValue key = upb_MapIterator_Key(self->map, iter);
upb_MessageValue val = upb_MapIterator_Value(self->map, iter);
upb_MessageValue other_val;
if (!upb_Map_Get(other->map, key, &other_val)) {
// Not present in other map.
return Qfalse;
}
if (!Msgval_IsEqual(val, other_val, self->value_type_info)) {
// Present but different value.
return Qfalse;
}
}
return Qtrue;
}
/*
* call-seq:
* Message.freeze => self
*
* Freezes the message object. We have to intercept this so we can pin the
* Ruby object into memory so we don't forget it's frozen.
*/
static VALUE Map_freeze(VALUE _self) {
Map* self = ruby_to_Map(_self);
if (!RB_OBJ_FROZEN(_self)) {
Arena_Pin(self->arena, _self);
RB_OBJ_FREEZE(_self);
}
return _self;
}
/*
* call-seq:
* Map.hash => hash_value
*
* Returns a hash value based on this map's contents.
*/
VALUE Map_hash(VALUE _self) {
Map* self = ruby_to_Map(_self);
uint64_t hash = 0;
size_t iter = kUpb_Map_Begin;
TypeInfo key_info = {self->key_type};
while (upb_MapIterator_Next(self->map, &iter)) {
upb_MessageValue key = upb_MapIterator_Key(self->map, iter);
upb_MessageValue val = upb_MapIterator_Value(self->map, iter);
hash = Msgval_GetHash(key, key_info, hash);
hash = Msgval_GetHash(val, self->value_type_info, hash);
}
return LL2NUM(hash);
}
/*
* call-seq:
* Map.to_h => {}
*
* Returns a Ruby Hash object containing all the values within the map
*/
VALUE Map_to_h(VALUE _self) {
Map* self = ruby_to_Map(_self);
return Map_CreateHash(self->map, self->key_type, self->value_type_info);
}
/*
* call-seq:
* Map.inspect => string
*
* Returns a string representing this map's elements. It will be formatted as
* "{key => value, key => value, ...}", with each key and value string
* representation computed by its own #inspect method.
*/
VALUE Map_inspect(VALUE _self) {
Map* self = ruby_to_Map(_self);
StringBuilder* builder = StringBuilder_New();
Map_Inspect(builder, self->map, self->key_type, self->value_type_info);
VALUE ret = StringBuilder_ToRubyString(builder);
StringBuilder_Free(builder);
return ret;
}
/*
* call-seq:
* Map.merge(other_map) => map
*
* Copies key/value pairs from other_map into a copy of this map. If a key is
* set in other_map and this map, the value from other_map overwrites the value
* in the new copy of this map. Returns the new copy of this map with merged
* contents.
*/
static VALUE Map_merge(VALUE _self, VALUE hashmap) {
VALUE dupped = Map_dup(_self);
return Map_merge_into_self(dupped, hashmap);
}
void Map_register(VALUE module) {
VALUE klass = rb_define_class_under(module, "Map", rb_cObject);
rb_define_alloc_func(klass, Map_alloc);
rb_gc_register_address(&cMap);
cMap = klass;
rb_define_method(klass, "initialize", Map_init, -1);
rb_define_method(klass, "each", Map_each, 0);
rb_define_method(klass, "keys", Map_keys, 0);
rb_define_method(klass, "values", Map_values, 0);
rb_define_method(klass, "[]", Map_index, 1);
rb_define_method(klass, "[]=", Map_index_set, 2);
rb_define_method(klass, "has_key?", Map_has_key, 1);
rb_define_method(klass, "delete", Map_delete, 1);
rb_define_method(klass, "clear", Map_clear, 0);
rb_define_method(klass, "length", Map_length, 0);
rb_define_method(klass, "size", Map_length, 0);
rb_define_method(klass, "dup", Map_dup, 0);
// Also define #clone so that we don't inherit Object#clone.
rb_define_method(klass, "clone", Map_dup, 0);
rb_define_method(klass, "==", Map_eq, 1);
rb_define_method(klass, "freeze", Map_freeze, 0);
rb_define_method(klass, "hash", Map_hash, 0);
rb_define_method(klass, "to_h", Map_to_h, 0);
rb_define_method(klass, "inspect", Map_inspect, 0);
rb_define_method(klass, "merge", Map_merge, 1);
rb_include_module(klass, rb_mEnumerable);
}

View File

@ -0,0 +1,66 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// 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 RUBY_PROTOBUF_MAP_H_
#define RUBY_PROTOBUF_MAP_H_
#include <ruby/ruby.h>
#include "protobuf.h"
#include "ruby-upb.h"
// Returns a Ruby wrapper object for the given map, which will be created if
// one does not exist already.
VALUE Map_GetRubyWrapper(upb_Map *map, upb_CType key_type, TypeInfo value_type,
VALUE arena);
// Gets the underlying upb_Map for this Ruby map object, which must have
// key/value type that match |field|. If this is not a map or the type doesn't
// match, raises an exception.
const upb_Map *Map_GetUpbMap(VALUE val, const upb_FieldDef *field,
upb_Arena *arena);
// Implements #inspect for this map by appending its contents to |b|.
void Map_Inspect(StringBuilder *b, const upb_Map *map, upb_CType key_type,
TypeInfo val_type);
// Returns a new Hash object containing the contents of this Map.
VALUE Map_CreateHash(const upb_Map *map, upb_CType key_type, TypeInfo val_info);
// Returns a deep copy of this Map object.
VALUE Map_deep_copy(VALUE obj);
// Ruby class of Google::Protobuf::Map.
extern VALUE cMap;
// Call at startup to register all types in this module.
void Map_register(VALUE module);
#endif // RUBY_PROTOBUF_MAP_H_

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,104 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// 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 RUBY_PROTOBUF_MESSAGE_H_
#define RUBY_PROTOBUF_MESSAGE_H_
#include <ruby/ruby.h>
#include "protobuf.h"
#include "ruby-upb.h"
// Gets the underlying upb_Message* and upb_MessageDef for the given Ruby
// message wrapper. Requires that |value| is indeed a message object.
const upb_Message* Message_Get(VALUE value, const upb_MessageDef** m);
// Like Message_Get(), but checks that the object is not frozen and returns a
// mutable pointer.
upb_Message* Message_GetMutable(VALUE value, const upb_MessageDef** m);
// Returns the Arena object for this message.
VALUE Message_GetArena(VALUE value);
// Converts |value| into a upb_Message value of the expected upb_MessageDef
// type, raising an error if this is not possible. Used when assigning |value|
// to a field of another message, which means the message must be of a
// particular type.
//
// This will perform automatic conversions in some cases (for example, Time ->
// Google::Protobuf::Timestamp). If any new message is created, it will be
// created on |arena|, and any existing message will have its arena fused with
// |arena|.
const upb_Message* Message_GetUpbMessage(VALUE value, const upb_MessageDef* m,
const char* name, upb_Arena* arena);
// Gets or constructs a Ruby wrapper object for the given message. The wrapper
// object will reference |arena| and ensure that it outlives this object.
VALUE Message_GetRubyWrapper(upb_Message* msg, const upb_MessageDef* m,
VALUE arena);
// Gets the given field from this message.
VALUE Message_getfield(VALUE _self, const upb_FieldDef* f);
// Implements #inspect for this message, printing the text to |b|.
void Message_PrintMessage(StringBuilder* b, const upb_Message* msg,
const upb_MessageDef* m);
// Returns a hash value for the given message.
uint64_t Message_Hash(const upb_Message* msg, const upb_MessageDef* m,
uint64_t seed);
// Returns a deep copy of the given message.
upb_Message* Message_deep_copy(const upb_Message* msg, const upb_MessageDef* m,
upb_Arena* arena);
// Returns true if these two messages are equal.
bool Message_Equal(const upb_Message* m1, const upb_Message* m2,
const upb_MessageDef* m);
// Checks that this Ruby object is a message, and raises an exception if not.
void Message_CheckClass(VALUE klass);
// Returns a new Hash object containing the contents of this message.
VALUE Scalar_CreateHash(upb_MessageValue val, TypeInfo type_info);
// Creates a message class or enum module for this descriptor, respectively.
VALUE build_class_from_descriptor(VALUE descriptor);
VALUE build_module_from_enumdesc(VALUE _enumdesc);
// Returns the Descriptor/EnumDescriptor for the given message class or enum
// module, respectively. Returns nil if this is not a message class or enum
// module.
VALUE MessageOrEnum_GetDescriptor(VALUE klass);
// Call at startup to register all types in this module.
void Message_register(VALUE protobuf);
#endif // RUBY_PROTOBUF_MESSAGE_H_

View File

@ -0,0 +1,480 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2014 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// 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 "protobuf.h"
#include <ruby/version.h>
#include "defs.h"
#include "map.h"
#include "message.h"
#include "repeated_field.h"
VALUE cParseError;
VALUE cTypeError;
const upb_FieldDef *map_field_key(const upb_FieldDef *field) {
const upb_MessageDef *entry = upb_FieldDef_MessageSubDef(field);
return upb_MessageDef_FindFieldByNumber(entry, 1);
}
const upb_FieldDef *map_field_value(const upb_FieldDef *field) {
const upb_MessageDef *entry = upb_FieldDef_MessageSubDef(field);
return upb_MessageDef_FindFieldByNumber(entry, 2);
}
// -----------------------------------------------------------------------------
// StringBuilder, for inspect
// -----------------------------------------------------------------------------
struct StringBuilder {
size_t size;
size_t cap;
char *data;
};
typedef struct StringBuilder StringBuilder;
static size_t StringBuilder_SizeOf(size_t cap) {
return sizeof(StringBuilder) + cap;
}
StringBuilder *StringBuilder_New() {
const size_t cap = 128;
StringBuilder *builder = malloc(sizeof(*builder));
builder->size = 0;
builder->cap = cap;
builder->data = malloc(builder->cap);
return builder;
}
void StringBuilder_Free(StringBuilder *b) {
free(b->data);
free(b);
}
void StringBuilder_Printf(StringBuilder *b, const char *fmt, ...) {
size_t have = b->cap - b->size;
size_t n;
va_list args;
va_start(args, fmt);
n = vsnprintf(&b->data[b->size], have, fmt, args);
va_end(args);
if (have <= n) {
while (have <= n) {
b->cap *= 2;
have = b->cap - b->size;
}
b->data = realloc(b->data, StringBuilder_SizeOf(b->cap));
va_start(args, fmt);
n = vsnprintf(&b->data[b->size], have, fmt, args);
va_end(args);
PBRUBY_ASSERT(n < have);
}
b->size += n;
}
VALUE StringBuilder_ToRubyString(StringBuilder *b) {
VALUE ret = rb_str_new(b->data, b->size);
rb_enc_associate(ret, rb_utf8_encoding());
return ret;
}
static void StringBuilder_PrintEnum(StringBuilder *b, int32_t val,
const upb_EnumDef *e) {
const upb_EnumValueDef *ev = upb_EnumDef_FindValueByNumber(e, val);
if (ev) {
StringBuilder_Printf(b, ":%s", upb_EnumValueDef_Name(ev));
} else {
StringBuilder_Printf(b, "%" PRId32, val);
}
}
void StringBuilder_PrintMsgval(StringBuilder *b, upb_MessageValue val,
TypeInfo info) {
switch (info.type) {
case kUpb_CType_Bool:
StringBuilder_Printf(b, "%s", val.bool_val ? "true" : "false");
break;
case kUpb_CType_Float: {
VALUE str = rb_inspect(DBL2NUM(val.float_val));
StringBuilder_Printf(b, "%s", RSTRING_PTR(str));
break;
}
case kUpb_CType_Double: {
VALUE str = rb_inspect(DBL2NUM(val.double_val));
StringBuilder_Printf(b, "%s", RSTRING_PTR(str));
break;
}
case kUpb_CType_Int32:
StringBuilder_Printf(b, "%" PRId32, val.int32_val);
break;
case kUpb_CType_UInt32:
StringBuilder_Printf(b, "%" PRIu32, val.uint32_val);
break;
case kUpb_CType_Int64:
StringBuilder_Printf(b, "%" PRId64, val.int64_val);
break;
case kUpb_CType_UInt64:
StringBuilder_Printf(b, "%" PRIu64, val.uint64_val);
break;
case kUpb_CType_String:
StringBuilder_Printf(b, "\"%.*s\"", (int)val.str_val.size,
val.str_val.data);
break;
case kUpb_CType_Bytes:
StringBuilder_Printf(b, "\"%.*s\"", (int)val.str_val.size,
val.str_val.data);
break;
case kUpb_CType_Enum:
StringBuilder_PrintEnum(b, val.int32_val, info.def.enumdef);
break;
case kUpb_CType_Message:
Message_PrintMessage(b, val.msg_val, info.def.msgdef);
break;
}
}
// -----------------------------------------------------------------------------
// Arena
// -----------------------------------------------------------------------------
typedef struct {
upb_Arena *arena;
VALUE pinned_objs;
} Arena;
static void Arena_mark(void *data) {
Arena *arena = data;
rb_gc_mark(arena->pinned_objs);
}
static void Arena_free(void *data) {
Arena *arena = data;
upb_Arena_Free(arena->arena);
xfree(arena);
}
static VALUE cArena;
const rb_data_type_t Arena_type = {
"Google::Protobuf::Internal::Arena",
{Arena_mark, Arena_free, NULL},
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
};
static void* ruby_upb_allocfunc(upb_alloc* alloc, void* ptr, size_t oldsize, size_t size) {
if (size == 0) {
xfree(ptr);
return NULL;
} else {
return xrealloc(ptr, size);
}
}
upb_alloc ruby_upb_alloc = {&ruby_upb_allocfunc};
static VALUE Arena_alloc(VALUE klass) {
Arena *arena = ALLOC(Arena);
arena->arena = upb_Arena_Init(NULL, 0, &ruby_upb_alloc);
arena->pinned_objs = Qnil;
return TypedData_Wrap_Struct(klass, &Arena_type, arena);
}
upb_Arena *Arena_get(VALUE _arena) {
Arena *arena;
TypedData_Get_Struct(_arena, Arena, &Arena_type, arena);
return arena->arena;
}
void Arena_fuse(VALUE _arena, upb_Arena *other) {
Arena *arena;
TypedData_Get_Struct(_arena, Arena, &Arena_type, arena);
if (!upb_Arena_Fuse(arena->arena, other)) {
rb_raise(rb_eRuntimeError,
"Unable to fuse arenas. This should never happen since Ruby does "
"not use initial blocks");
}
}
VALUE Arena_new() { return Arena_alloc(cArena); }
void Arena_Pin(VALUE _arena, VALUE obj) {
Arena *arena;
TypedData_Get_Struct(_arena, Arena, &Arena_type, arena);
if (arena->pinned_objs == Qnil) {
arena->pinned_objs = rb_ary_new();
}
rb_ary_push(arena->pinned_objs, obj);
}
void Arena_register(VALUE module) {
VALUE internal = rb_define_module_under(module, "Internal");
VALUE klass = rb_define_class_under(internal, "Arena", rb_cObject);
rb_define_alloc_func(klass, Arena_alloc);
rb_gc_register_address(&cArena);
cArena = klass;
}
// -----------------------------------------------------------------------------
// Object Cache
// -----------------------------------------------------------------------------
// A pointer -> Ruby Object cache that keeps references to Ruby wrapper
// objects. This allows us to look up any Ruby wrapper object by the address
// of the object it is wrapping. That way we can avoid ever creating two
// different wrapper objects for the same C object, which saves memory and
// preserves object identity.
//
// We use WeakMap for the cache. For Ruby <2.7 we also need a secondary Hash
// to store WeakMap keys because Ruby <2.7 WeakMap doesn't allow non-finalizable
// keys.
//
// We also need the secondary Hash if sizeof(long) < sizeof(VALUE), because this
// means it may not be possible to fit a pointer into a Fixnum. Keys are
// pointers, and if they fit into a Fixnum, Ruby doesn't collect them, but if
// they overflow and require allocating a Bignum, they could get collected
// prematurely, thus removing the cache entry. This happens on 64-bit Windows,
// on which pointers are 64 bits but longs are 32 bits. In this case, we enable
// the secondary Hash to hold the keys and prevent them from being collected.
#if RUBY_API_VERSION_CODE >= 20700 && SIZEOF_LONG >= SIZEOF_VALUE
#define USE_SECONDARY_MAP 0
#else
#define USE_SECONDARY_MAP 1
#endif
#if USE_SECONDARY_MAP
// Maps Numeric -> Object. The object is then used as a key into the WeakMap.
// This is needed for Ruby <2.7 where a number cannot be a key to WeakMap.
// The object is used only for its identity; it does not contain any data.
VALUE secondary_map = Qnil;
// Mutations to the map are under a mutex, because SeconaryMap_MaybeGC()
// iterates over the map which cannot happen in parallel with insertions, or
// Ruby will throw:
// can't add a new key into hash during iteration (RuntimeError)
VALUE secondary_map_mutex = Qnil;
// Lambda that will GC entries from the secondary map that are no longer present
// in the primary map.
VALUE gc_secondary_map_lambda = Qnil;
ID length;
extern VALUE weak_obj_cache;
static void SecondaryMap_Init() {
rb_gc_register_address(&secondary_map);
rb_gc_register_address(&gc_secondary_map_lambda);
rb_gc_register_address(&secondary_map_mutex);
secondary_map = rb_hash_new();
gc_secondary_map_lambda = rb_eval_string(
"->(secondary, weak) {\n"
" secondary.delete_if { |k, v| !weak.key?(v) }\n"
"}\n");
secondary_map_mutex = rb_mutex_new();
length = rb_intern("length");
}
// The secondary map is a regular Hash, and will never shrink on its own.
// The main object cache is a WeakMap that will automatically remove entries
// when the target object is no longer reachable, but unless we manually
// remove the corresponding entries from the secondary map, it will grow
// without bound.
//
// To avoid this unbounded growth we periodically remove entries from the
// secondary map that are no longer present in the WeakMap. The logic of
// how often to perform this GC is an artbirary tuning parameter that
// represents a straightforward CPU/memory tradeoff.
//
// Requires: secondary_map_mutex is held.
static void SecondaryMap_MaybeGC() {
PBRUBY_ASSERT(rb_mutex_locked_p(secondary_map_mutex) == Qtrue);
size_t weak_len = NUM2ULL(rb_funcall(weak_obj_cache, length, 0));
size_t secondary_len = RHASH_SIZE(secondary_map);
if (secondary_len < weak_len) {
// Logically this case should not be possible: a valid entry cannot exist in
// the weak table unless there is a corresponding entry in the secondary
// table. It should *always* be the case that secondary_len >= weak_len.
//
// However ObjectSpace::WeakMap#length (and therefore weak_len) is
// unreliable: it overreports its true length by including non-live objects.
// However these non-live objects are not yielded in iteration, so we may
// have previously deleted them from the secondary map in a previous
// invocation of SecondaryMap_MaybeGC().
//
// In this case, we can't measure any waste, so we just return.
return;
}
size_t waste = secondary_len - weak_len;
// GC if we could remove at least 2000 entries or 20% of the table size
// (whichever is greater). Since the cost of the GC pass is O(N), we
// want to make sure that we condition this on overall table size, to
// avoid O(N^2) CPU costs.
size_t threshold = PBRUBY_MAX(secondary_len * 0.2, 2000);
if (waste > threshold) {
rb_funcall(gc_secondary_map_lambda, rb_intern("call"), 2, secondary_map,
weak_obj_cache);
}
}
// Requires: secondary_map_mutex is held by this thread iff create == true.
static VALUE SecondaryMap_Get(VALUE key, bool create) {
PBRUBY_ASSERT(!create || rb_mutex_locked_p(secondary_map_mutex) == Qtrue);
VALUE ret = rb_hash_lookup(secondary_map, key);
if (ret == Qnil && create) {
SecondaryMap_MaybeGC();
ret = rb_class_new_instance(0, NULL, rb_cObject);
rb_hash_aset(secondary_map, key, ret);
}
return ret;
}
#endif
// Requires: secondary_map_mutex is held by this thread iff create == true.
static VALUE ObjectCache_GetKey(const void *key, bool create) {
VALUE key_val = (VALUE)key;
PBRUBY_ASSERT((key_val & 3) == 0);
VALUE ret = LL2NUM(key_val >> 2);
#if USE_SECONDARY_MAP
ret = SecondaryMap_Get(ret, create);
#endif
return ret;
}
// Public ObjectCache API.
VALUE weak_obj_cache = Qnil;
ID item_get;
ID item_set;
static void ObjectCache_Init() {
rb_gc_register_address(&weak_obj_cache);
VALUE klass = rb_eval_string("ObjectSpace::WeakMap");
weak_obj_cache = rb_class_new_instance(0, NULL, klass);
item_get = rb_intern("[]");
item_set = rb_intern("[]=");
#if USE_SECONDARY_MAP
SecondaryMap_Init();
#endif
}
void ObjectCache_Add(const void *key, VALUE val) {
PBRUBY_ASSERT(ObjectCache_Get(key) == Qnil);
#if USE_SECONDARY_MAP
rb_mutex_lock(secondary_map_mutex);
#endif
VALUE key_rb = ObjectCache_GetKey(key, true);
rb_funcall(weak_obj_cache, item_set, 2, key_rb, val);
#if USE_SECONDARY_MAP
rb_mutex_unlock(secondary_map_mutex);
#endif
PBRUBY_ASSERT(ObjectCache_Get(key) == val);
}
// Returns the cached object for this key, if any. Otherwise returns Qnil.
VALUE ObjectCache_Get(const void *key) {
VALUE key_rb = ObjectCache_GetKey(key, false);
return rb_funcall(weak_obj_cache, item_get, 1, key_rb);
}
/*
* call-seq:
* Google::Protobuf.discard_unknown(msg)
*
* Discard unknown fields in the given message object and recursively discard
* unknown fields in submessages.
*/
static VALUE Google_Protobuf_discard_unknown(VALUE self, VALUE msg_rb) {
const upb_MessageDef *m;
upb_Message *msg = Message_GetMutable(msg_rb, &m);
if (!upb_Message_DiscardUnknown(msg, m, 128)) {
rb_raise(rb_eRuntimeError, "Messages nested too deeply.");
}
return Qnil;
}
/*
* call-seq:
* Google::Protobuf.deep_copy(obj) => copy_of_obj
*
* Performs a deep copy of a RepeatedField instance, a Map instance, or a
* message object, recursively copying its members.
*/
VALUE Google_Protobuf_deep_copy(VALUE self, VALUE obj) {
VALUE klass = CLASS_OF(obj);
if (klass == cRepeatedField) {
return RepeatedField_deep_copy(obj);
} else if (klass == cMap) {
return Map_deep_copy(obj);
} else {
VALUE new_arena_rb = Arena_new();
upb_Arena *new_arena = Arena_get(new_arena_rb);
const upb_MessageDef *m;
const upb_Message *msg = Message_Get(obj, &m);
upb_Message *new_msg = Message_deep_copy(msg, m, new_arena);
return Message_GetRubyWrapper(new_msg, m, new_arena_rb);
}
}
// -----------------------------------------------------------------------------
// Initialization/entry point.
// -----------------------------------------------------------------------------
// This must be named "Init_protobuf_c" because the Ruby module is named
// "protobuf_c" -- the VM looks for this symbol in our .so.
__attribute__((visibility("default"))) void Init_protobuf_c() {
ObjectCache_Init();
VALUE google = rb_define_module("Google");
VALUE protobuf = rb_define_module_under(google, "Protobuf");
Arena_register(protobuf);
Defs_register(protobuf);
RepeatedField_register(protobuf);
Map_register(protobuf);
Message_register(protobuf);
cParseError = rb_const_get(protobuf, rb_intern("ParseError"));
rb_gc_register_mark_object(cParseError);
cTypeError = rb_const_get(protobuf, rb_intern("TypeError"));
rb_gc_register_mark_object(cTypeError);
rb_define_singleton_method(protobuf, "discard_unknown",
Google_Protobuf_discard_unknown, 1);
rb_define_singleton_method(protobuf, "deep_copy", Google_Protobuf_deep_copy,
1);
}

View File

@ -0,0 +1,120 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2014 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// 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 __GOOGLE_PROTOBUF_RUBY_PROTOBUF_H__
#define __GOOGLE_PROTOBUF_RUBY_PROTOBUF_H__
#include <ruby/encoding.h>
#include <ruby/ruby.h>
#include <ruby/vm.h>
#include "defs.h"
#include "ruby-upb.h"
// These operate on a map field (i.e., a repeated field of submessages whose
// submessage type is a map-entry msgdef).
const upb_FieldDef* map_field_key(const upb_FieldDef* field);
const upb_FieldDef* map_field_value(const upb_FieldDef* field);
// -----------------------------------------------------------------------------
// Arena
// -----------------------------------------------------------------------------
// A Ruby object that wraps an underlying upb_Arena. Any objects that are
// allocated from this arena should reference the Arena in rb_gc_mark(), to
// ensure that the object's underlying memory outlives any Ruby object that can
// reach it.
VALUE Arena_new();
upb_Arena* Arena_get(VALUE arena);
// Fuses this arena to another, throwing a Ruby exception if this is not
// possible.
void Arena_fuse(VALUE arena, upb_Arena* other);
// Pins this Ruby object to the lifetime of this arena, so that as long as the
// arena is alive this object will not be collected.
//
// We use this to guarantee that the "frozen" bit on the object will be
// remembered, even if the user drops their reference to this precise object.
void Arena_Pin(VALUE arena, VALUE obj);
// -----------------------------------------------------------------------------
// ObjectCache
// -----------------------------------------------------------------------------
// Global object cache from upb array/map/message/symtab to wrapper object.
//
// This is a conceptually "weak" cache, in that it does not prevent "val" from
// being collected (though in Ruby <2.7 is it effectively strong, due to
// implementation limitations).
// Adds an entry to the cache. The "arena" parameter must give the arena that
// "key" was allocated from. In Ruby <2.7.0, it will be used to remove the key
// from the cache when the arena is destroyed.
void ObjectCache_Add(const void* key, VALUE val);
// Returns the cached object for this key, if any. Otherwise returns Qnil.
VALUE ObjectCache_Get(const void* key);
// -----------------------------------------------------------------------------
// StringBuilder, for inspect
// -----------------------------------------------------------------------------
struct StringBuilder;
typedef struct StringBuilder StringBuilder;
StringBuilder* StringBuilder_New();
void StringBuilder_Free(StringBuilder* b);
void StringBuilder_Printf(StringBuilder* b, const char* fmt, ...);
VALUE StringBuilder_ToRubyString(StringBuilder* b);
void StringBuilder_PrintMsgval(StringBuilder* b, upb_MessageValue val,
TypeInfo info);
// -----------------------------------------------------------------------------
// Utilities.
// -----------------------------------------------------------------------------
extern VALUE cTypeError;
#ifdef NDEBUG
#define PBRUBY_ASSERT(expr) \
do { \
} while (false && (expr))
#else
#define PBRUBY_ASSERT(expr) assert(expr)
#endif
#define PBRUBY_MAX(x, y) (((x) > (y)) ? (x) : (y))
#define UPB_UNUSED(var) (void)var
#endif // __GOOGLE_PROTOBUF_RUBY_PROTOBUF_H__

View File

@ -0,0 +1,657 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2014 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// 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 "repeated_field.h"
#include "convert.h"
#include "defs.h"
#include "message.h"
#include "protobuf.h"
// -----------------------------------------------------------------------------
// Repeated field container type.
// -----------------------------------------------------------------------------
typedef struct {
const upb_Array* array; // Can get as mutable when non-frozen.
TypeInfo type_info;
VALUE type_class; // To GC-root the msgdef/enumdef in type_info.
VALUE arena; // To GC-root the upb_Array.
} RepeatedField;
VALUE cRepeatedField;
static void RepeatedField_mark(void* _self) {
RepeatedField* self = (RepeatedField*)_self;
rb_gc_mark(self->type_class);
rb_gc_mark(self->arena);
}
const rb_data_type_t RepeatedField_type = {
"Google::Protobuf::RepeatedField",
{RepeatedField_mark, RUBY_DEFAULT_FREE, NULL},
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
};
static RepeatedField* ruby_to_RepeatedField(VALUE _self) {
RepeatedField* self;
TypedData_Get_Struct(_self, RepeatedField, &RepeatedField_type, self);
return self;
}
static upb_Array* RepeatedField_GetMutable(VALUE _self) {
rb_check_frozen(_self);
return (upb_Array*)ruby_to_RepeatedField(_self)->array;
}
VALUE RepeatedField_alloc(VALUE klass) {
RepeatedField* self = ALLOC(RepeatedField);
self->arena = Qnil;
self->type_class = Qnil;
self->array = NULL;
return TypedData_Wrap_Struct(klass, &RepeatedField_type, self);
}
VALUE RepeatedField_GetRubyWrapper(upb_Array* array, TypeInfo type_info,
VALUE arena) {
PBRUBY_ASSERT(array);
VALUE val = ObjectCache_Get(array);
if (val == Qnil) {
val = RepeatedField_alloc(cRepeatedField);
RepeatedField* self;
ObjectCache_Add(array, val);
TypedData_Get_Struct(val, RepeatedField, &RepeatedField_type, self);
self->array = array;
self->arena = arena;
self->type_info = type_info;
if (self->type_info.type == kUpb_CType_Message) {
self->type_class = Descriptor_DefToClass(type_info.def.msgdef);
}
}
PBRUBY_ASSERT(ruby_to_RepeatedField(val)->type_info.type == type_info.type);
PBRUBY_ASSERT(ruby_to_RepeatedField(val)->type_info.def.msgdef ==
type_info.def.msgdef);
return val;
}
static VALUE RepeatedField_new_this_type(RepeatedField* from) {
VALUE arena_rb = Arena_new();
upb_Array* array = upb_Array_New(Arena_get(arena_rb), from->type_info.type);
VALUE ret = RepeatedField_GetRubyWrapper(array, from->type_info, arena_rb);
PBRUBY_ASSERT(ruby_to_RepeatedField(ret)->type_class == from->type_class);
return ret;
}
void RepeatedField_Inspect(StringBuilder* b, const upb_Array* array,
TypeInfo info) {
bool first = true;
StringBuilder_Printf(b, "[");
size_t n = array ? upb_Array_Size(array) : 0;
for (size_t i = 0; i < n; i++) {
if (first) {
first = false;
} else {
StringBuilder_Printf(b, ", ");
}
StringBuilder_PrintMsgval(b, upb_Array_Get(array, i), info);
}
StringBuilder_Printf(b, "]");
}
VALUE RepeatedField_deep_copy(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
VALUE new_rptfield = RepeatedField_new_this_type(self);
RepeatedField* new_self = ruby_to_RepeatedField(new_rptfield);
VALUE arena_rb = new_self->arena;
upb_Array* new_array = RepeatedField_GetMutable(new_rptfield);
upb_Arena* arena = Arena_get(arena_rb);
size_t elements = upb_Array_Size(self->array);
upb_Array_Resize(new_array, elements, arena);
size_t size = upb_Array_Size(self->array);
for (size_t i = 0; i < size; i++) {
upb_MessageValue msgval = upb_Array_Get(self->array, i);
upb_MessageValue copy = Msgval_DeepCopy(msgval, self->type_info, arena);
upb_Array_Set(new_array, i, copy);
}
return new_rptfield;
}
const upb_Array* RepeatedField_GetUpbArray(VALUE val, const upb_FieldDef* field,
upb_Arena* arena) {
RepeatedField* self;
TypeInfo type_info = TypeInfo_get(field);
if (!RB_TYPE_P(val, T_DATA) || !RTYPEDDATA_P(val) ||
RTYPEDDATA_TYPE(val) != &RepeatedField_type) {
rb_raise(cTypeError, "Expected repeated field array");
}
self = ruby_to_RepeatedField(val);
if (self->type_info.type != type_info.type) {
rb_raise(cTypeError, "Repeated field array has wrong element type");
}
if (self->type_info.def.msgdef != type_info.def.msgdef) {
rb_raise(cTypeError, "Repeated field array has wrong message/enum class");
}
Arena_fuse(self->arena, arena);
return self->array;
}
static int index_position(VALUE _index, RepeatedField* repeated_field) {
int index = NUM2INT(_index);
if (index < 0) index += upb_Array_Size(repeated_field->array);
return index;
}
static VALUE RepeatedField_subarray(RepeatedField* self, long beg, long len) {
size_t size = upb_Array_Size(self->array);
VALUE ary = rb_ary_new2(size);
long i;
for (i = beg; i < beg + len; i++) {
upb_MessageValue msgval = upb_Array_Get(self->array, i);
VALUE elem = Convert_UpbToRuby(msgval, self->type_info, self->arena);
rb_ary_push(ary, elem);
}
return ary;
}
/*
* call-seq:
* RepeatedField.each(&block)
*
* Invokes the block once for each element of the repeated field. RepeatedField
* also includes Enumerable; combined with this method, the repeated field thus
* acts like an ordinary Ruby sequence.
*/
static VALUE RepeatedField_each(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
int size = upb_Array_Size(self->array);
int i;
for (i = 0; i < size; i++) {
upb_MessageValue msgval = upb_Array_Get(self->array, i);
VALUE val = Convert_UpbToRuby(msgval, self->type_info, self->arena);
rb_yield(val);
}
return _self;
}
/*
* call-seq:
* RepeatedField.[](index) => value
*
* Accesses the element at the given index. Returns nil on out-of-bounds
*/
static VALUE RepeatedField_index(int argc, VALUE* argv, VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
long size = upb_Array_Size(self->array);
VALUE arg = argv[0];
long beg, len;
if (argc == 1) {
if (FIXNUM_P(arg)) {
/* standard case */
upb_MessageValue msgval;
int index = index_position(argv[0], self);
if (index < 0 || (size_t)index >= upb_Array_Size(self->array)) {
return Qnil;
}
msgval = upb_Array_Get(self->array, index);
return Convert_UpbToRuby(msgval, self->type_info, self->arena);
} else {
/* check if idx is Range */
switch (rb_range_beg_len(arg, &beg, &len, size, 0)) {
case Qfalse:
break;
case Qnil:
return Qnil;
default:
return RepeatedField_subarray(self, beg, len);
}
}
}
/* assume 2 arguments */
beg = NUM2LONG(argv[0]);
len = NUM2LONG(argv[1]);
if (beg < 0) {
beg += size;
}
if (beg >= size) {
return Qnil;
}
return RepeatedField_subarray(self, beg, len);
}
/*
* call-seq:
* RepeatedField.[]=(index, value)
*
* Sets the element at the given index. On out-of-bounds assignments, extends
* the array and fills the hole (if any) with default values.
*/
static VALUE RepeatedField_index_set(VALUE _self, VALUE _index, VALUE val) {
RepeatedField* self = ruby_to_RepeatedField(_self);
int size = upb_Array_Size(self->array);
upb_Array* array = RepeatedField_GetMutable(_self);
upb_Arena* arena = Arena_get(self->arena);
upb_MessageValue msgval = Convert_RubyToUpb(val, "", self->type_info, arena);
int index = index_position(_index, self);
if (index < 0 || index >= (INT_MAX - 1)) {
return Qnil;
}
if (index >= size) {
upb_Array_Resize(array, index + 1, arena);
upb_MessageValue fill;
memset(&fill, 0, sizeof(fill));
for (int i = size; i < index; i++) {
// Fill default values.
// TODO(haberman): should this happen at the upb level?
upb_Array_Set(array, i, fill);
}
}
upb_Array_Set(array, index, msgval);
return Qnil;
}
/*
* call-seq:
* RepeatedField.push(value, ...)
*
* Adds a new element to the repeated field.
*/
static VALUE RepeatedField_push_vararg(int argc, VALUE* argv, VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
upb_Arena* arena = Arena_get(self->arena);
upb_Array* array = RepeatedField_GetMutable(_self);
int i;
for (i = 0; i < argc; i++) {
upb_MessageValue msgval =
Convert_RubyToUpb(argv[i], "", self->type_info, arena);
upb_Array_Append(array, msgval, arena);
}
return _self;
}
/*
* call-seq:
* RepeatedField.<<(value)
*
* Adds a new element to the repeated field.
*/
static VALUE RepeatedField_push(VALUE _self, VALUE val) {
RepeatedField* self = ruby_to_RepeatedField(_self);
upb_Arena* arena = Arena_get(self->arena);
upb_Array* array = RepeatedField_GetMutable(_self);
upb_MessageValue msgval = Convert_RubyToUpb(val, "", self->type_info, arena);
upb_Array_Append(array, msgval, arena);
return _self;
}
/*
* Private ruby method, used by RepeatedField.pop
*/
static VALUE RepeatedField_pop_one(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
size_t size = upb_Array_Size(self->array);
upb_Array* array = RepeatedField_GetMutable(_self);
upb_MessageValue last;
VALUE ret;
if (size == 0) {
return Qnil;
}
last = upb_Array_Get(self->array, size - 1);
ret = Convert_UpbToRuby(last, self->type_info, self->arena);
upb_Array_Resize(array, size - 1, Arena_get(self->arena));
return ret;
}
/*
* call-seq:
* RepeatedField.replace(list)
*
* Replaces the contents of the repeated field with the given list of elements.
*/
static VALUE RepeatedField_replace(VALUE _self, VALUE list) {
RepeatedField* self = ruby_to_RepeatedField(_self);
upb_Array* array = RepeatedField_GetMutable(_self);
int i;
Check_Type(list, T_ARRAY);
upb_Array_Resize(array, 0, Arena_get(self->arena));
for (i = 0; i < RARRAY_LEN(list); i++) {
RepeatedField_push(_self, rb_ary_entry(list, i));
}
return list;
}
/*
* call-seq:
* RepeatedField.clear
*
* Clears (removes all elements from) this repeated field.
*/
static VALUE RepeatedField_clear(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
upb_Array* array = RepeatedField_GetMutable(_self);
upb_Array_Resize(array, 0, Arena_get(self->arena));
return _self;
}
/*
* call-seq:
* RepeatedField.length
*
* Returns the length of this repeated field.
*/
static VALUE RepeatedField_length(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
return INT2NUM(upb_Array_Size(self->array));
}
/*
* call-seq:
* RepeatedField.dup => repeated_field
*
* Duplicates this repeated field with a shallow copy. References to all
* non-primitive element objects (e.g., submessages) are shared.
*/
static VALUE RepeatedField_dup(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
VALUE new_rptfield = RepeatedField_new_this_type(self);
RepeatedField* new_rptfield_self = ruby_to_RepeatedField(new_rptfield);
upb_Array* new_array = RepeatedField_GetMutable(new_rptfield);
upb_Arena* arena = Arena_get(new_rptfield_self->arena);
int size = upb_Array_Size(self->array);
int i;
Arena_fuse(self->arena, arena);
for (i = 0; i < size; i++) {
upb_MessageValue msgval = upb_Array_Get(self->array, i);
upb_Array_Append(new_array, msgval, arena);
}
return new_rptfield;
}
/*
* call-seq:
* RepeatedField.to_ary => array
*
* Used when converted implicitly into array, e.g. compared to an Array.
* Also called as a fallback of Object#to_a
*/
VALUE RepeatedField_to_ary(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
int size = upb_Array_Size(self->array);
VALUE ary = rb_ary_new2(size);
int i;
for (i = 0; i < size; i++) {
upb_MessageValue msgval = upb_Array_Get(self->array, i);
VALUE val = Convert_UpbToRuby(msgval, self->type_info, self->arena);
rb_ary_push(ary, val);
}
return ary;
}
/*
* call-seq:
* RepeatedField.==(other) => boolean
*
* Compares this repeated field to another. Repeated fields are equal if their
* element types are equal, their lengths are equal, and each element is equal.
* Elements are compared as per normal Ruby semantics, by calling their :==
* methods (or performing a more efficient comparison for primitive types).
*
* Repeated fields with dissimilar element types are never equal, even if value
* comparison (for example, between integers and floats) would have otherwise
* indicated that every element has equal value.
*/
VALUE RepeatedField_eq(VALUE _self, VALUE _other) {
RepeatedField* self;
RepeatedField* other;
if (_self == _other) {
return Qtrue;
}
if (TYPE(_other) == T_ARRAY) {
VALUE self_ary = RepeatedField_to_ary(_self);
return rb_equal(self_ary, _other);
}
self = ruby_to_RepeatedField(_self);
other = ruby_to_RepeatedField(_other);
size_t n = upb_Array_Size(self->array);
if (self->type_info.type != other->type_info.type ||
self->type_class != other->type_class ||
upb_Array_Size(other->array) != n) {
return Qfalse;
}
for (size_t i = 0; i < n; i++) {
upb_MessageValue val1 = upb_Array_Get(self->array, i);
upb_MessageValue val2 = upb_Array_Get(other->array, i);
if (!Msgval_IsEqual(val1, val2, self->type_info)) {
return Qfalse;
}
}
return Qtrue;
}
/*
* call-seq:
* RepeatedField.freeze => self
*
* Freezes the repeated field. We have to intercept this so we can pin the Ruby
* object into memory so we don't forget it's frozen.
*/
static VALUE RepeatedField_freeze(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
if (!RB_OBJ_FROZEN(_self)) {
Arena_Pin(self->arena, _self);
RB_OBJ_FREEZE(_self);
}
return _self;
}
/*
* call-seq:
* RepeatedField.hash => hash_value
*
* Returns a hash value computed from this repeated field's elements.
*/
VALUE RepeatedField_hash(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
uint64_t hash = 0;
size_t n = upb_Array_Size(self->array);
for (size_t i = 0; i < n; i++) {
upb_MessageValue val = upb_Array_Get(self->array, i);
hash = Msgval_GetHash(val, self->type_info, hash);
}
return LL2NUM(hash);
}
/*
* call-seq:
* RepeatedField.+(other) => repeated field
*
* Returns a new repeated field that contains the concatenated list of this
* repeated field's elements and other's elements. The other (second) list may
* be either another repeated field or a Ruby array.
*/
VALUE RepeatedField_plus(VALUE _self, VALUE list) {
VALUE dupped_ = RepeatedField_dup(_self);
if (TYPE(list) == T_ARRAY) {
int i;
for (i = 0; i < RARRAY_LEN(list); i++) {
VALUE elem = rb_ary_entry(list, i);
RepeatedField_push(dupped_, elem);
}
} else if (RB_TYPE_P(list, T_DATA) && RTYPEDDATA_P(list) &&
RTYPEDDATA_TYPE(list) == &RepeatedField_type) {
RepeatedField* self = ruby_to_RepeatedField(_self);
RepeatedField* list_rptfield = ruby_to_RepeatedField(list);
RepeatedField* dupped = ruby_to_RepeatedField(dupped_);
upb_Array* dupped_array = RepeatedField_GetMutable(dupped_);
upb_Arena* arena = Arena_get(dupped->arena);
Arena_fuse(list_rptfield->arena, arena);
int size = upb_Array_Size(list_rptfield->array);
int i;
if (self->type_info.type != list_rptfield->type_info.type ||
self->type_class != list_rptfield->type_class) {
rb_raise(rb_eArgError,
"Attempt to append RepeatedField with different element type.");
}
for (i = 0; i < size; i++) {
upb_MessageValue msgval = upb_Array_Get(list_rptfield->array, i);
upb_Array_Append(dupped_array, msgval, arena);
}
} else {
rb_raise(rb_eArgError, "Unknown type appending to RepeatedField");
}
return dupped_;
}
/*
* call-seq:
* RepeatedField.concat(other) => self
*
* concats the passed in array to self. Returns a Ruby array.
*/
VALUE RepeatedField_concat(VALUE _self, VALUE list) {
int i;
Check_Type(list, T_ARRAY);
for (i = 0; i < RARRAY_LEN(list); i++) {
RepeatedField_push(_self, rb_ary_entry(list, i));
}
return _self;
}
/*
* call-seq:
* RepeatedField.new(type, type_class = nil, initial_elems = [])
*
* Creates a new repeated field. The provided type must be a Ruby symbol, and
* can take on the same values as those accepted by FieldDescriptor#type=. If
* the type is :message or :enum, type_class must be non-nil, and must be the
* Ruby class or module returned by Descriptor#msgclass or
* EnumDescriptor#enummodule, respectively. An initial list of elements may also
* be provided.
*/
VALUE RepeatedField_init(int argc, VALUE* argv, VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self);
upb_Arena* arena;
VALUE ary = Qnil;
self->arena = Arena_new();
arena = Arena_get(self->arena);
if (argc < 1) {
rb_raise(rb_eArgError, "Expected at least 1 argument.");
}
self->type_info = TypeInfo_FromClass(argc, argv, 0, &self->type_class, &ary);
self->array = upb_Array_New(arena, self->type_info.type);
ObjectCache_Add(self->array, _self);
if (ary != Qnil) {
if (!RB_TYPE_P(ary, T_ARRAY)) {
rb_raise(rb_eArgError, "Expected array as initialize argument");
}
for (int i = 0; i < RARRAY_LEN(ary); i++) {
RepeatedField_push(_self, rb_ary_entry(ary, i));
}
}
return Qnil;
}
void RepeatedField_register(VALUE module) {
VALUE klass = rb_define_class_under(module, "RepeatedField", rb_cObject);
rb_define_alloc_func(klass, RepeatedField_alloc);
rb_gc_register_address(&cRepeatedField);
cRepeatedField = klass;
rb_define_method(klass, "initialize", RepeatedField_init, -1);
rb_define_method(klass, "each", RepeatedField_each, 0);
rb_define_method(klass, "[]", RepeatedField_index, -1);
rb_define_method(klass, "at", RepeatedField_index, -1);
rb_define_method(klass, "[]=", RepeatedField_index_set, 2);
rb_define_method(klass, "push", RepeatedField_push_vararg, -1);
rb_define_method(klass, "<<", RepeatedField_push, 1);
rb_define_private_method(klass, "pop_one", RepeatedField_pop_one, 0);
rb_define_method(klass, "replace", RepeatedField_replace, 1);
rb_define_method(klass, "clear", RepeatedField_clear, 0);
rb_define_method(klass, "length", RepeatedField_length, 0);
rb_define_method(klass, "size", RepeatedField_length, 0);
rb_define_method(klass, "dup", RepeatedField_dup, 0);
// Also define #clone so that we don't inherit Object#clone.
rb_define_method(klass, "clone", RepeatedField_dup, 0);
rb_define_method(klass, "==", RepeatedField_eq, 1);
rb_define_method(klass, "to_ary", RepeatedField_to_ary, 0);
rb_define_method(klass, "freeze", RepeatedField_freeze, 0);
rb_define_method(klass, "hash", RepeatedField_hash, 0);
rb_define_method(klass, "+", RepeatedField_plus, 1);
rb_define_method(klass, "concat", RepeatedField_concat, 1);
rb_include_module(klass, rb_mEnumerable);
}

View File

@ -0,0 +1,63 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// 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 RUBY_PROTOBUF_REPEATED_FIELD_H_
#define RUBY_PROTOBUF_REPEATED_FIELD_H_
#include <ruby/ruby.h>
#include "protobuf.h"
#include "ruby-upb.h"
// Returns a Ruby wrapper object for the given upb_Array, which will be created
// if one does not exist already.
VALUE RepeatedField_GetRubyWrapper(upb_Array* msg, TypeInfo type_info,
VALUE arena);
// Gets the underlying upb_Array for this Ruby RepeatedField object, which must
// have a type that matches |f|. If this is not a repeated field or the type
// doesn't match, raises an exception.
const upb_Array* RepeatedField_GetUpbArray(VALUE value, const upb_FieldDef* f,
upb_Arena* arena);
// Implements #inspect for this repeated field by appending its contents to |b|.
void RepeatedField_Inspect(StringBuilder* b, const upb_Array* array,
TypeInfo info);
// Returns a deep copy of this RepeatedField object.
VALUE RepeatedField_deep_copy(VALUE obj);
// Ruby class of Google::Protobuf::RepeatedField.
extern VALUE cRepeatedField;
// Call at startup to register all types in this module.
void RepeatedField_register(VALUE module);
#endif // RUBY_PROTOBUF_REPEATED_FIELD_H_

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,52 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2017 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// 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 <string.h>
// On x86-64 Linux with glibc, we link against the 2.2.5 version of memcpy so
// that we avoid depending on the 2.14 version of the symbol. This way,
// distributions that are using pre-2.14 versions of glibc can successfully use
// the gem we distribute
// (https://github.com/protocolbuffers/protobuf/issues/2783).
//
// This wrapper is enabled by passing the linker flags -Wl,-wrap,memcpy in
// extconf.rb.
#ifdef __linux__
#if defined(__x86_64__) && defined(__GNU_LIBRARY__)
__asm__(".symver memcpy,memcpy@GLIBC_2.2.5");
void *__wrap_memcpy(void *dest, const void *src, size_t n) {
return memcpy(dest, src, n);
}
#else
void *__wrap_memcpy(void *dest, const void *src, size_t n) {
return memmove(dest, src, n);
}
#endif
#endif

View File

@ -0,0 +1,27 @@
Gem::Specification.new do |s|
s.name = "google-protobuf"
s.version = "3.20.3"
git_tag = "v#{s.version.to_s.sub('.rc.', '-rc')}" # Converts X.Y.Z.rc.N to vX.Y.Z-rcN, used for the git tag
s.licenses = ["BSD-3-Clause"]
s.summary = "Protocol Buffers"
s.description = "Protocol Buffers are Google's data interchange format."
s.homepage = "https://developers.google.com/protocol-buffers"
s.authors = ["Protobuf Authors"]
s.email = "protobuf@googlegroups.com"
s.metadata = { "source_code_uri" => "https://github.com/protocolbuffers/protobuf/tree/#{git_tag}/ruby" }
s.require_paths = ["lib"]
s.files = Dir.glob('lib/**/*.rb')
if RUBY_PLATFORM == "java"
s.platform = "java"
s.files += ["lib/google/protobuf_java.jar"]
else
s.files += Dir.glob('ext/**/*')
s.extensions= ["ext/google/protobuf_c/extconf.rb"]
s.add_development_dependency "rake-compiler-dock", "= 1.2.1" end
s.test_files = ["tests/basic.rb",
"tests/stress.rb",
"tests/generated_code_test.rb"]
s.required_ruby_version = '>= 2.3'
s.add_development_dependency "rake-compiler", "~> 1.1.0"
s.add_development_dependency "test-unit", '~> 3.0', '>= 3.0.9'
end

View File

@ -0,0 +1,79 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2008 Google Inc. All rights reserved.
# https://developers.google.com/protocol-buffers/
#
# 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 mixins before we hook them into the java & c code
require 'google/protobuf/message_exts'
# We define these before requiring the platform-specific modules.
# That way the module init can grab references to these.
module Google
module Protobuf
class Error < StandardError; end
class ParseError < Error; end
class TypeError < ::TypeError; end
end
end
if RUBY_PLATFORM == "java"
require 'json'
require 'google/protobuf_java'
else
begin
require "google/#{RUBY_VERSION.sub(/\.\d+$/, '')}/protobuf_c"
rescue LoadError
require 'google/protobuf_c'
end
end
require 'google/protobuf/descriptor_dsl'
require 'google/protobuf/repeated_field'
module Google
module Protobuf
def self.encode(msg, options = {})
msg.to_proto(options)
end
def self.encode_json(msg, options = {})
msg.to_json(options)
end
def self.decode(klass, proto, options = {})
klass.decode(proto, options)
end
def self.decode_json(klass, json, options = {})
klass.decode_json(json, options)
end
end
end

View File

@ -0,0 +1,465 @@
#!/usr/bin/ruby
#
# Code that implements the DSL for defining proto messages.
# Suppress warning: loading in progress, circular require considered harmful.
# This circular require is intentional to avoid missing dependency.
begin
old_verbose, $VERBOSE = $VERBOSE, nil
require 'google/protobuf/descriptor_pb'
ensure
$VERBOSE = old_verbose
end
module Google
module Protobuf
module Internal
class AtomicCounter
def initialize
@n = 0
@mu = Mutex.new
end
def get_and_increment
n = @n
@mu.synchronize {
@n += 1
}
return n
end
end
class Builder
@@file_number = AtomicCounter.new
def initialize(pool)
@pool = pool
@default_file = nil # Constructed lazily
end
def add_file(name, options={}, &block)
builder = FileBuilder.new(@pool, name, options)
builder.instance_eval(&block)
internal_add_file(builder)
end
def add_message(name, &block)
internal_default_file.add_message(name, &block)
end
def add_enum(name, &block)
internal_default_file.add_enum(name, &block)
end
# ---- Internal methods, not part of the DSL ----
def build
if @default_file
internal_add_file(@default_file)
end
end
private def internal_add_file(file_builder)
proto = file_builder.build
serialized = Google::Protobuf::FileDescriptorProto.encode(proto)
@pool.add_serialized_file(serialized)
end
private def internal_default_file
number = @@file_number.get_and_increment
filename = "ruby_default_file#{number}.proto"
@default_file ||= FileBuilder.new(@pool, filename)
end
end
class FileBuilder
def initialize(pool, name, options={})
@pool = pool
@file_proto = Google::Protobuf::FileDescriptorProto.new(
name: name,
syntax: options.fetch(:syntax, "proto3")
)
end
def add_message(name, &block)
builder = MessageBuilder.new(name, self, @file_proto)
builder.instance_eval(&block)
builder.internal_add_synthetic_oneofs
end
def add_enum(name, &block)
EnumBuilder.new(name, @file_proto).instance_eval(&block)
end
# ---- Internal methods, not part of the DSL ----
# These methods fix up the file descriptor to account for differences
# between the DSL and FileDescriptorProto.
# The DSL can omit a package name; here we infer what the package is if
# was not specified.
def infer_package(names)
# Package is longest common prefix ending in '.', if any.
if not names.empty?
min, max = names.minmax
last_common_dot = nil
min.size.times { |i|
if min[i] != max[i] then break end
if min[i] == "." then last_common_dot = i end
}
if last_common_dot
return min.slice(0, last_common_dot)
end
end
nil
end
def rewrite_enum_default(field)
if field.type != :TYPE_ENUM or !field.has_default_value? or !field.has_type_name?
return
end
value = field.default_value
type_name = field.type_name
if value.empty? or value[0].ord < "0".ord or value[0].ord > "9".ord
return
end
if type_name.empty? || type_name[0] != "."
return
end
type_name = type_name[1..-1]
as_int = Integer(value) rescue return
enum_desc = @pool.lookup(type_name)
if enum_desc.is_a?(Google::Protobuf::EnumDescriptor)
# Enum was defined in a previous file.
name = enum_desc.lookup_value(as_int)
if name
# Update the default value in the proto.
field.default_value = name
end
else
# See if enum was defined in this file.
@file_proto.enum_type.each { |enum_proto|
if enum_proto.name == type_name
enum_proto.value.each { |enum_value_proto|
if enum_value_proto.number == as_int
# Update the default value in the proto.
field.default_value = enum_value_proto.name
return
end
}
# We found the right enum, but no value matched.
return
end
}
end
end
# Historically we allowed enum defaults to be specified as a number.
# In retrospect this was a mistake as descriptors require defaults to
# be specified as a label. This can make a difference if multiple
# labels have the same number.
#
# Here we do a pass over all enum defaults and rewrite numeric defaults
# by looking up their labels. This is complicated by the fact that the
# enum definition can live in either the symtab or the file_proto.
#
# We take advantage of the fact that this is called *before* enums or
# messages are nested in other messages, so we only have to iterate
# one level deep.
def rewrite_enum_defaults
@file_proto.message_type.each { |msg|
msg.field.each { |field|
rewrite_enum_default(field)
}
}
end
# We have to do some relatively complicated logic here for backward
# compatibility.
#
# In descriptor.proto, messages are nested inside other messages if that is
# what the original .proto file looks like. For example, suppose we have this
# foo.proto:
#
# package foo;
# message Bar {
# message Baz {}
# }
#
# The descriptor for this must look like this:
#
# file {
# name: "test.proto"
# package: "foo"
# message_type {
# name: "Bar"
# nested_type {
# name: "Baz"
# }
# }
# }
#
# However, the Ruby generated code has always generated messages in a flat,
# non-nested way:
#
# Google::Protobuf::DescriptorPool.generated_pool.build do
# add_message "foo.Bar" do
# end
# add_message "foo.Bar.Baz" do
# end
# end
#
# Here we need to do a translation where we turn this generated code into the
# above descriptor. We need to infer that "foo" is the package name, and not
# a message itself. */
def split_parent_name(msg_or_enum)
name = msg_or_enum.name
idx = name.rindex(?.)
if idx
return name[0...idx], name[idx+1..-1]
else
return nil, name
end
end
def get_parent_msg(msgs_by_name, name, parent_name)
parent_msg = msgs_by_name[parent_name]
if parent_msg.nil?
raise "To define name #{name}, there must be a message named #{parent_name} to enclose it"
end
return parent_msg
end
def fix_nesting
# Calculate and update package.
msgs_by_name = @file_proto.message_type.map { |msg| [msg.name, msg] }.to_h
enum_names = @file_proto.enum_type.map { |enum_proto| enum_proto.name }
package = infer_package(msgs_by_name.keys + enum_names)
if package
@file_proto.package = package
end
# Update nesting based on package.
final_msgs = Google::Protobuf::RepeatedField.new(:message, Google::Protobuf::DescriptorProto)
final_enums = Google::Protobuf::RepeatedField.new(:message, Google::Protobuf::EnumDescriptorProto)
# Note: We don't iterate over msgs_by_name.values because we want to
# preserve order as listed in the DSL.
@file_proto.message_type.each { |msg|
parent_name, msg.name = split_parent_name(msg)
if parent_name == package
final_msgs << msg
else
get_parent_msg(msgs_by_name, msg.name, parent_name).nested_type << msg
end
}
@file_proto.enum_type.each { |enum|
parent_name, enum.name = split_parent_name(enum)
if parent_name == package
final_enums << enum
else
get_parent_msg(msgs_by_name, enum.name, parent_name).enum_type << enum
end
}
@file_proto.message_type = final_msgs
@file_proto.enum_type = final_enums
end
def internal_file_proto
@file_proto
end
def build
rewrite_enum_defaults
fix_nesting
return @file_proto
end
end
class MessageBuilder
def initialize(name, file_builder, file_proto)
@file_builder = file_builder
@msg_proto = Google::Protobuf::DescriptorProto.new(
:name => name
)
file_proto.message_type << @msg_proto
end
def optional(name, type, number, type_class=nil, options=nil)
internal_add_field(:LABEL_OPTIONAL, name, type, number, type_class, options)
end
def proto3_optional(name, type, number, type_class=nil, options=nil)
internal_add_field(:LABEL_OPTIONAL, name, type, number, type_class, options,
proto3_optional: true)
end
def required(name, type, number, type_class=nil, options=nil)
internal_add_field(:LABEL_REQUIRED, name, type, number, type_class, options)
end
def repeated(name, type, number, type_class = nil, options=nil)
internal_add_field(:LABEL_REPEATED, name, type, number, type_class, options)
end
def oneof(name, &block)
OneofBuilder.new(name, self).instance_eval(&block)
end
# Defines a new map field on this message type with the given key and
# value types, tag number, and type class (for message and enum value
# types). The key type must be :int32/:uint32/:int64/:uint64, :bool, or
# :string. The value type type must be a Ruby symbol (as accepted by
# FieldDescriptor#type=) and the type_class must be a string, if
# present (as accepted by FieldDescriptor#submsg_name=).
def map(name, key_type, value_type, number, value_type_class = nil)
if key_type == :float or key_type == :double or key_type == :enum or
key_type == :message
raise ArgError, "Not an acceptable key type: " + key_type
end
entry_name = "#{@msg_proto.name}_MapEntry_#{name}"
@file_builder.add_message entry_name do
optional :key, key_type, 1
optional :value, value_type, 2, value_type_class
end
options = @file_builder.internal_file_proto.message_type.last.options ||= MessageOptions.new
options.map_entry = true
repeated name, :message, number, entry_name
end
# ---- Internal methods, not part of the DSL ----
def internal_add_synthetic_oneofs
# We have to build a set of all names, to ensure that synthetic oneofs
# are not creating conflicts
names = {}
@msg_proto.field.each { |field| names[field.name] = true }
@msg_proto.oneof_decl.each { |oneof| names[oneof.name] = true }
@msg_proto.field.each { |field|
if field.proto3_optional
# Prepend '_' until we are no longer conflicting.
oneof_name = field.name
while names[oneof_name]
oneof_name = "_" + oneof_name
end
names[oneof_name] = true
field.oneof_index = @msg_proto.oneof_decl.size
@msg_proto.oneof_decl << Google::Protobuf::OneofDescriptorProto.new(
name: oneof_name
)
end
}
end
def internal_add_field(label, name, type, number, type_class, options,
oneof_index: nil, proto3_optional: false)
# Allow passing either:
# - (name, type, number, options) or
# - (name, type, number, type_class, options)
if options.nil? and type_class.instance_of?(Hash)
options = type_class;
type_class = nil;
end
field_proto = Google::Protobuf::FieldDescriptorProto.new(
:label => label,
:name => name,
:type => ("TYPE_" + type.to_s.upcase).to_sym,
:number => number
)
if type_class
# Make it an absolute type name by prepending a dot.
field_proto.type_name = "." + type_class
end
if oneof_index
field_proto.oneof_index = oneof_index
end
if proto3_optional
field_proto.proto3_optional = true
end
if options
if options.key?(:default)
default = options[:default]
if !default.instance_of?(String)
# Call #to_s since all defaults are strings in the descriptor.
default = default.to_s
end
# XXX: we should be C-escaping bytes defaults.
field_proto.default_value = default.dup.force_encoding("UTF-8")
end
if options.key?(:json_name)
field_proto.json_name = options[:json_name]
end
end
@msg_proto.field << field_proto
end
def internal_msg_proto
@msg_proto
end
end
class OneofBuilder
def initialize(name, msg_builder)
@msg_builder = msg_builder
oneof_proto = Google::Protobuf::OneofDescriptorProto.new(
:name => name
)
msg_proto = msg_builder.internal_msg_proto
@oneof_index = msg_proto.oneof_decl.size
msg_proto.oneof_decl << oneof_proto
end
def optional(name, type, number, type_class=nil, options=nil)
@msg_builder.internal_add_field(
:LABEL_OPTIONAL, name, type, number, type_class, options,
oneof_index: @oneof_index)
end
end
class EnumBuilder
def initialize(name, file_proto)
@enum_proto = Google::Protobuf::EnumDescriptorProto.new(
:name => name
)
file_proto.enum_type << @enum_proto
end
def value(name, number)
enum_value_proto = Google::Protobuf::EnumValueDescriptorProto.new(
name: name,
number: number
)
@enum_proto.value << enum_value_proto
end
end
end
# Re-open the class (the rest of the class is implemented in C)
class DescriptorPool
def build(&block)
builder = Internal::Builder.new(self)
builder.instance_eval(&block)
builder.build
end
end
end
end

View File

@ -0,0 +1,53 @@
# Protocol Buffers - Google's data interchange format
# Copyright 2008 Google Inc. All rights reserved.
# https://developers.google.com/protocol-buffers/
#
# 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.
module Google
module Protobuf
module MessageExts
#this is only called in jruby; mri loades the ClassMethods differently
def self.included(klass)
klass.extend(ClassMethods)
end
module ClassMethods
end
def to_json(options = {})
self.class.encode_json(self, options)
end
def to_proto(options = {})
self.class.encode(self, options)
end
end
end
end

View File

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

View File

@ -0,0 +1,240 @@
#!/usr/bin/ruby
# Protocol Buffers - Google's data interchange format
# Copyright 2008 Google Inc. All rights reserved.
# https://developers.google.com/protocol-buffers/
#
# 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 'google/protobuf/any_pb'
require 'google/protobuf/duration_pb'
require 'google/protobuf/field_mask_pb'
require 'google/protobuf/struct_pb'
require 'google/protobuf/timestamp_pb'
module Google
module Protobuf
Any.class_eval do
def self.pack(msg, type_url_prefix='type.googleapis.com/')
any = self.new
any.pack(msg, type_url_prefix)
any
end
def pack(msg, type_url_prefix='type.googleapis.com/')
if type_url_prefix.empty? or type_url_prefix[-1] != '/' then
self.type_url = "#{type_url_prefix}/#{msg.class.descriptor.name}"
else
self.type_url = "#{type_url_prefix}#{msg.class.descriptor.name}"
end
self.value = msg.to_proto
end
def unpack(klass)
if self.is(klass) then
klass.decode(self.value)
else
nil
end
end
def type_name
return self.type_url.split("/")[-1]
end
def is(klass)
return self.type_name == klass.descriptor.name
end
end
Timestamp.class_eval do
if RUBY_VERSION < "2.5"
def to_time
Time.at(self.to_f)
end
else
def to_time
Time.at(seconds, nanos, :nanosecond)
end
end
def self.from_time(time)
new.from_time(time)
end
def from_time(time)
self.seconds = time.to_i
self.nanos = time.nsec
self
end
def to_i
self.seconds
end
def to_f
self.seconds + (self.nanos.quo(1_000_000_000))
end
end
Duration.class_eval do
def to_f
self.seconds + (self.nanos.to_f / 1_000_000_000)
end
end
class UnexpectedStructType < Google::Protobuf::Error; end
Value.class_eval do
def to_ruby(recursive = false)
case self.kind
when :struct_value
if recursive
self.struct_value.to_h
else
self.struct_value
end
when :list_value
if recursive
self.list_value.to_a
else
self.list_value
end
when :null_value
nil
when :number_value
self.number_value
when :string_value
self.string_value
when :bool_value
self.bool_value
else
raise UnexpectedStructType
end
end
def self.from_ruby(value)
self.new.from_ruby(value)
end
def from_ruby(value)
case value
when NilClass
self.null_value = :NULL_VALUE
when Numeric
self.number_value = value
when String
self.string_value = value
when TrueClass
self.bool_value = true
when FalseClass
self.bool_value = false
when Struct
self.struct_value = value
when Hash
self.struct_value = Struct.from_hash(value)
when ListValue
self.list_value = value
when Array
self.list_value = ListValue.from_a(value)
else
raise UnexpectedStructType
end
self
end
end
Struct.class_eval do
def [](key)
self.fields[key].to_ruby
rescue NoMethodError
nil
end
def []=(key, value)
unless key.is_a?(String)
raise UnexpectedStructType, "Struct keys must be strings."
end
self.fields[key] ||= Google::Protobuf::Value.new
self.fields[key].from_ruby(value)
end
def to_h
ret = {}
self.fields.each { |key, val| ret[key] = val.to_ruby(true) }
ret
end
def self.from_hash(hash)
ret = Struct.new
hash.each { |key, val| ret[key] = val }
ret
end
def has_key?(key)
self.fields.has_key?(key)
end
end
ListValue.class_eval do
include Enumerable
def length
self.values.length
end
def [](index)
self.values[index].to_ruby
end
def []=(index, value)
self.values[index].from_ruby(value)
end
def <<(value)
wrapper = Google::Protobuf::Value.new
wrapper.from_ruby(value)
self.values << wrapper
end
def each
self.values.each { |x| yield(x.to_ruby) }
end
def to_a
self.values.map { |x| x.to_ruby(true) }
end
def self.from_a(arr)
ret = ListValue.new
arr.each { |val| ret << val }
ret
end
end
end
end

88
deps/protobuf/ruby/pom.xml vendored Normal file
View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.google</groupId>
<artifactId>google</artifactId>
<version>1</version>
</parent>
<groupId>com.google.protobuf.jruby</groupId>
<artifactId>protobuf-jruby</artifactId>
<version>3.20.3</version>
<name>Protocol Buffer JRuby native extension</name>
<description>
Protocol Buffers are a way of encoding structured data in an efficient yet
extensible format.
</description>
<inceptionYear>2014</inceptionYear>
<url>https://developers.google.com/protocol-buffers/</url>
<licenses>
<license>
<name>BSD-3-Clause</name>
<url>https://opensource.org/licenses/BSD-3-Clause</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<url>https://github.com/protocolbuffers/protobuf</url>
<connection>
scm:git:https://github.com/protocolbuffers/protobuf.git
</connection>
</scm>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<ruby.sources>lib/google</ruby.sources>
<jar.finalName>protobuf_java</jar.finalName>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<finalName>${jar.finalName}</finalName>
<outputDirectory>${ruby.sources}</outputDirectory>
<appendAssemblyId>false</appendAssemblyId>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!-- Ordering of dependencies matters to update_version.py -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>3.20.3</version>
</dependency>
<dependency>
<groupId>org.jruby</groupId>
<artifactId>jruby-complete</artifactId>
<version>9.2.20.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,225 @@
/*
* Protocol Buffers - Google's data interchange format
* Copyright 2014 Google Inc. All rights reserved.
* https://developers.google.com/protocol-buffers/
*
* 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.
*/
package com.google.protobuf.jruby;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.OneofDescriptor;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import java.util.HashMap;
import java.util.Map;
@JRubyClass(name = "Descriptor", include = "Enumerable")
public class RubyDescriptor extends RubyObject {
public static void createRubyDescriptor(Ruby runtime) {
RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
RubyClass cDescriptor = protobuf.defineClassUnder("Descriptor", runtime.getObject(), new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new RubyDescriptor(runtime, klazz);
}
});
cDescriptor.includeModule(runtime.getEnumerable());
cDescriptor.defineAnnotatedMethods(RubyDescriptor.class);
cFieldDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::FieldDescriptor");
cOneofDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::OneofDescriptor");
}
public RubyDescriptor(Ruby runtime, RubyClass klazz) {
super(runtime, klazz);
}
/*
* call-seq:
* Descriptor.name => name
*
* Returns the name of this message type as a fully-qualified string (e.g.,
* My.Package.MessageType).
*/
@JRubyMethod(name = "name")
public IRubyObject getName(ThreadContext context) {
return name;
}
/*
* call-seq:
* Descriptor.lookup(name) => FieldDescriptor
*
* Returns the field descriptor for the field with the given name, if present,
* or nil if none.
*/
@JRubyMethod
public IRubyObject lookup(ThreadContext context, IRubyObject fieldName) {
return Helpers.nullToNil(fieldDescriptors.get(fieldName), context.nil);
}
/*
* call-seq:
* Descriptor.msgclass => message_klass
*
* Returns the Ruby class created for this message type. Valid only once the
* message type has been added to a pool.
*/
@JRubyMethod
public IRubyObject msgclass(ThreadContext context) {
return klazz;
}
/*
* call-seq:
* Descriptor.each(&block)
*
* Iterates over fields in this message type, yielding to the block on each one.
*/
@JRubyMethod
public IRubyObject each(ThreadContext context, Block block) {
for (Map.Entry<IRubyObject, RubyFieldDescriptor> entry : fieldDescriptors.entrySet()) {
block.yield(context, entry.getValue());
}
return context.nil;
}
/*
* call-seq:
* Descriptor.file_descriptor
*
* Returns the FileDescriptor object this message belongs to.
*/
@JRubyMethod(name = "file_descriptor")
public IRubyObject getFileDescriptor(ThreadContext context) {
return RubyFileDescriptor.getRubyFileDescriptor(context, descriptor);
}
/*
* call-seq:
* Descriptor.each_oneof(&block) => nil
*
* Invokes the given block for each oneof in this message type, passing the
* corresponding OneofDescriptor.
*/
@JRubyMethod(name = "each_oneof")
public IRubyObject eachOneof(ThreadContext context, Block block) {
for (RubyOneofDescriptor oneofDescriptor : oneofDescriptors.values()) {
block.yieldSpecific(context, oneofDescriptor);
}
return context.nil;
}
/*
* call-seq:
* Descriptor.lookup_oneof(name) => OneofDescriptor
*
* Returns the oneof descriptor for the oneof with the given name, if present,
* or nil if none.
*/
@JRubyMethod(name = "lookup_oneof")
public IRubyObject lookupOneof(ThreadContext context, IRubyObject name) {
return Helpers.nullToNil(oneofDescriptors.get(Utils.symToString(name)), context.nil);
}
protected FieldDescriptor getField(String name) {
return descriptor.findFieldByName(name);
}
protected void setDescriptor(ThreadContext context, Descriptor descriptor, RubyDescriptorPool pool) {
Ruby runtime = context.runtime;
Map<FieldDescriptor, RubyFieldDescriptor> cache = new HashMap();
this.descriptor = descriptor;
// Populate the field caches
fieldDescriptors = new HashMap<IRubyObject, RubyFieldDescriptor>();
oneofDescriptors = new HashMap<IRubyObject, RubyOneofDescriptor>();
for (FieldDescriptor fieldDescriptor : descriptor.getFields()) {
RubyFieldDescriptor fd = (RubyFieldDescriptor) cFieldDescriptor.newInstance(context, Block.NULL_BLOCK);
fd.setDescriptor(context, fieldDescriptor, pool);
fieldDescriptors.put(runtime.newString(fieldDescriptor.getName()), fd);
cache.put(fieldDescriptor, fd);
}
for (OneofDescriptor oneofDescriptor : descriptor.getRealOneofs()) {
RubyOneofDescriptor ood = (RubyOneofDescriptor) cOneofDescriptor.newInstance(context, Block.NULL_BLOCK);
ood.setDescriptor(context, oneofDescriptor, cache);
oneofDescriptors.put(runtime.newString(oneofDescriptor.getName()), ood);
}
// Make sure our class is built
this.klazz = buildClassFromDescriptor(context);
}
protected void setName(IRubyObject name) {
this.name = name;
}
private RubyClass buildClassFromDescriptor(ThreadContext context) {
Ruby runtime = context.runtime;
ObjectAllocator allocator = new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new RubyMessage(runtime, klazz, descriptor);
}
};
// rb_define_class_id
RubyClass klass = RubyClass.newClass(runtime, runtime.getObject());
klass.setAllocator(allocator);
klass.makeMetaClass(runtime.getObject().getMetaClass());
klass.inherit(runtime.getObject());
RubyModule messageExts = runtime.getClassFromPath("Google::Protobuf::MessageExts");
klass.include(new IRubyObject[] {messageExts});
klass.instance_variable_set(runtime.newString(Utils.DESCRIPTOR_INSTANCE_VAR), this);
klass.defineAnnotatedMethods(RubyMessage.class);
// Workaround for https://github.com/jruby/jruby/issues/7154
klass.searchMethod("respond_to?").setIsBuiltin(false);
return klass;
}
private static RubyClass cFieldDescriptor;
private static RubyClass cOneofDescriptor;
private Descriptor descriptor;
private IRubyObject name;
private Map<IRubyObject, RubyFieldDescriptor> fieldDescriptors;
private Map<IRubyObject, RubyOneofDescriptor> oneofDescriptors;
private RubyClass klazz;
}

View File

@ -0,0 +1,180 @@
/*
* Protocol Buffers - Google's data interchange format
* Copyright 2014 Google Inc. All rights reserved.
* https://developers.google.com/protocol-buffers/
*
* 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.
*/
package com.google.protobuf.jruby;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.DescriptorValidationException;
import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.FileDescriptor;
import com.google.protobuf.InvalidProtocolBufferException;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.runtime.*;
import org.jruby.runtime.builtin.IRubyObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@JRubyClass(name = "DescriptorPool")
public class RubyDescriptorPool extends RubyObject {
public static void createRubyDescriptorPool(Ruby runtime) {
RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
RubyClass cDescriptorPool = protobuf.defineClassUnder("DescriptorPool", runtime.getObject(), new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new RubyDescriptorPool(runtime, klazz);
}
});
cDescriptorPool.defineAnnotatedMethods(RubyDescriptorPool.class);
descriptorPool = (RubyDescriptorPool) cDescriptorPool.newInstance(runtime.getCurrentContext(), Block.NULL_BLOCK);
cDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Descriptor");
cEnumDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::EnumDescriptor");
}
public RubyDescriptorPool(Ruby runtime, RubyClass klazz) {
super(runtime, klazz);
this.fileDescriptors = new ArrayList<>();
this.symtab = new HashMap<IRubyObject, IRubyObject>();
}
@JRubyMethod
public IRubyObject build(ThreadContext context, Block block) {
RubyClass cBuilder = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::Internal::Builder");
RubyBasicObject ctx = (RubyBasicObject) cBuilder.newInstance(context, this, Block.NULL_BLOCK);
ctx.instance_eval(context, block);
ctx.callMethod(context, "build"); // Needs to be called to support the deprecated syntax
return context.nil;
}
/*
* call-seq:
* DescriptorPool.lookup(name) => descriptor
*
* Finds a Descriptor or EnumDescriptor by name and returns it, or nil if none
* exists with the given name.
*
* This currently lazy loads the ruby descriptor objects as they are requested.
* This allows us to leave the heavy lifting to the java library
*/
@JRubyMethod
public IRubyObject lookup(ThreadContext context, IRubyObject name) {
return Helpers.nullToNil(symtab.get(name), context.nil);
}
/*
* call-seq:
* DescriptorPool.generated_pool => descriptor_pool
*
* Class method that returns the global DescriptorPool. This is a singleton into
* which generated-code message and enum types are registered. The user may also
* register types in this pool for convenience so that they do not have to hold
* a reference to a private pool instance.
*/
@JRubyMethod(meta = true, name = "generated_pool")
public static IRubyObject generatedPool(ThreadContext context, IRubyObject recv) {
return descriptorPool;
}
@JRubyMethod(required = 1)
public IRubyObject add_serialized_file (ThreadContext context, IRubyObject data ) {
byte[] bin = data.convertToString().getBytes();
try {
FileDescriptorProto.Builder builder = FileDescriptorProto.newBuilder().mergeFrom(bin);
registerFileDescriptor(context, builder);
} catch (InvalidProtocolBufferException e) {
throw RaiseException.from(context.runtime, (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::ParseError"), e.getMessage());
}
return context.nil;
}
protected void registerFileDescriptor(ThreadContext context, FileDescriptorProto.Builder builder) {
final FileDescriptor fd;
try {
fd = FileDescriptor.buildFrom(builder.build(), existingFileDescriptors());
} catch (DescriptorValidationException e) {
throw context.runtime.newRuntimeError(e.getMessage());
}
String packageName = fd.getPackage();
if (!packageName.isEmpty()) {
packageName = packageName + ".";
}
// Need to make sure enums are registered first in case anything references them
for (EnumDescriptor ed : fd.getEnumTypes()) registerEnumDescriptor(context, ed, packageName);
for (Descriptor message : fd.getMessageTypes()) registerDescriptor(context, message, packageName);
// Mark this as a loaded file
fileDescriptors.add(fd);
}
private void registerDescriptor(ThreadContext context, Descriptor descriptor, String parentPath) {
String fullName = parentPath + descriptor.getName();
String fullPath = fullName + ".";
RubyString name = context.runtime.newString(fullName);
RubyDescriptor des = (RubyDescriptor) cDescriptor.newInstance(context, Block.NULL_BLOCK);
des.setName(name);
des.setDescriptor(context, descriptor, this);
symtab.put(name, des);
// Need to make sure enums are registered first in case anything references them
for (EnumDescriptor ed : descriptor.getEnumTypes()) registerEnumDescriptor(context, ed, fullPath);
for (Descriptor message : descriptor.getNestedTypes()) registerDescriptor(context, message, fullPath);
}
private void registerEnumDescriptor(ThreadContext context, EnumDescriptor descriptor, String parentPath) {
RubyString name = context.runtime.newString(parentPath + descriptor.getName());
RubyEnumDescriptor des = (RubyEnumDescriptor) cEnumDescriptor.newInstance(context, Block.NULL_BLOCK);
des.setName(name);
des.setDescriptor(context, descriptor);
symtab.put(name, des);
}
private FileDescriptor[] existingFileDescriptors() {
return fileDescriptors.toArray(new FileDescriptor[fileDescriptors.size()]);
}
private static RubyClass cDescriptor;
private static RubyClass cEnumDescriptor;
private static RubyDescriptorPool descriptorPool;
private List<FileDescriptor> fileDescriptors;
private Map<IRubyObject, IRubyObject> symtab;
}

View File

@ -0,0 +1,78 @@
/*
* Protocol Buffers - Google's data interchange format
* Copyright 2014 Google Inc. All rights reserved.
* https://developers.google.com/protocol-buffers/
*
* 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.
*/
package com.google.protobuf.jruby;
import org.jruby.RubyModule;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
public class RubyEnum {
/*
* call-seq:
* Enum.lookup(number) => name
*
* This module method, provided on each generated enum module, looks up an enum
* value by number and returns its name as a Ruby symbol, or nil if not found.
*/
@JRubyMethod(meta = true)
public static IRubyObject lookup(ThreadContext context, IRubyObject recv, IRubyObject number) {
RubyEnumDescriptor rubyEnumDescriptor = (RubyEnumDescriptor) getDescriptor(context, recv);
return rubyEnumDescriptor.numberToName(context, number);
}
/*
* call-seq:
* Enum.resolve(name) => number
*
* This module method, provided on each generated enum module, looks up an enum
* value by name (as a Ruby symbol) and returns its name, or nil if not found.
*/
@JRubyMethod(meta = true)
public static IRubyObject resolve(ThreadContext context, IRubyObject recv, IRubyObject name) {
RubyEnumDescriptor rubyEnumDescriptor = (RubyEnumDescriptor) getDescriptor(context, recv);
return rubyEnumDescriptor.nameToNumber(context, name);
}
/*
* call-seq:
* Enum.descriptor
*
* This module method, provided on each generated enum module, returns the
* EnumDescriptor corresponding to this enum type.
*/
@JRubyMethod(meta = true, name = "descriptor")
public static IRubyObject getDescriptor(ThreadContext context, IRubyObject recv) {
return ((RubyModule) recv).getInstanceVariable(Utils.DESCRIPTOR_INSTANCE_VAR);
}
}

View File

@ -0,0 +1,181 @@
/*
* Protocol Buffers - Google's data interchange format
* Copyright 2014 Google Inc. All rights reserved.
* https://developers.google.com/protocol-buffers/
*
* 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.
*/
package com.google.protobuf.jruby;
import com.google.protobuf.DescriptorProtos.EnumDescriptorProto;
import com.google.protobuf.Descriptors.EnumDescriptor;
import com.google.protobuf.Descriptors.EnumValueDescriptor;
import com.google.protobuf.Descriptors.FileDescriptor;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
import org.jruby.RubyObject;
import org.jruby.RubyNumeric;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
@JRubyClass(name = "EnumDescriptor", include = "Enumerable")
public class RubyEnumDescriptor extends RubyObject {
public static void createRubyEnumDescriptor(Ruby runtime) {
RubyModule mProtobuf = runtime.getClassFromPath("Google::Protobuf");
RubyClass cEnumDescriptor = mProtobuf.defineClassUnder("EnumDescriptor", runtime.getObject(), new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new RubyEnumDescriptor(runtime, klazz);
}
});
cEnumDescriptor.includeModule(runtime.getEnumerable());
cEnumDescriptor.defineAnnotatedMethods(RubyEnumDescriptor.class);
}
public RubyEnumDescriptor(Ruby runtime, RubyClass klazz) {
super(runtime, klazz);
}
/*
* call-seq:
* EnumDescriptor.name => name
*
* Returns the name of this enum type.
*/
@JRubyMethod(name = "name")
public IRubyObject getName(ThreadContext context) {
return this.name;
}
/*
* call-seq:
* EnumDescriptor.each(&block)
*
* Iterates over key => value mappings in this enum's definition, yielding to
* the block with (key, value) arguments for each one.
*/
@JRubyMethod
public IRubyObject each(ThreadContext context, Block block) {
Ruby runtime = context.runtime;
for (EnumValueDescriptor enumValueDescriptor : descriptor.getValues()) {
block.yield(context, runtime.newArray(runtime.newSymbol(enumValueDescriptor.getName()),
runtime.newFixnum(enumValueDescriptor.getNumber())));
}
return context.nil;
}
/*
* call-seq:
* EnumDescriptor.enummodule => module
*
* Returns the Ruby module corresponding to this enum type. Cannot be called
* until the enum descriptor has been added to a pool.
*/
@JRubyMethod
public IRubyObject enummodule(ThreadContext context) {
return module;
}
/*
* call-seq:
* EnumDescriptor.file_descriptor
*
* Returns the FileDescriptor object this enum belongs to.
*/
@JRubyMethod(name = "file_descriptor")
public IRubyObject getFileDescriptor(ThreadContext context) {
return RubyFileDescriptor.getRubyFileDescriptor(context, descriptor);
}
public boolean isValidValue(ThreadContext context, IRubyObject value) {
EnumValueDescriptor enumValue;
if (Utils.isRubyNum(value)) {
enumValue = descriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value));
} else {
enumValue = descriptor.findValueByName(value.asJavaString());
}
return enumValue != null;
}
protected IRubyObject nameToNumber(ThreadContext context, IRubyObject name) {
EnumValueDescriptor value = descriptor.findValueByName(name.asJavaString());
return value == null ? context.nil : context.runtime.newFixnum(value.getNumber());
}
protected IRubyObject numberToName(ThreadContext context, IRubyObject number) {
EnumValueDescriptor value = descriptor.findValueByNumber(RubyNumeric.num2int(number));
return value == null ? context.nil : context.runtime.newSymbol(value.getName());
}
protected void setDescriptor(ThreadContext context, EnumDescriptor descriptor) {
this.descriptor = descriptor;
this.module = buildModuleFromDescriptor(context);
}
protected void setName(IRubyObject name) {
this.name = name;
}
private RubyModule buildModuleFromDescriptor(ThreadContext context) {
Ruby runtime = context.runtime;
RubyModule enumModule = RubyModule.newModule(runtime);
boolean defaultValueRequiredButNotFound = descriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3;
for (EnumValueDescriptor value : descriptor.getValues()) {
String name = value.getName();
// Make sure its a valid constant name before trying to create it
if (Character.isUpperCase(name.codePointAt(0))) {
enumModule.defineConstant(name, runtime.newFixnum(value.getNumber()));
} else {
runtime.getWarnings().warn("Enum value " + name + " does not start with an uppercase letter as is required for Ruby constants.");
}
if (value.getNumber() == 0) {
defaultValueRequiredButNotFound = false;
}
}
if (defaultValueRequiredButNotFound) {
throw Utils.createTypeError(context, "Enum definition " + name + " does not contain a value for '0'");
}
enumModule.instance_variable_set(runtime.newString(Utils.DESCRIPTOR_INSTANCE_VAR), this);
enumModule.defineAnnotatedMethods(RubyEnum.class);
return enumModule;
}
private EnumDescriptor descriptor;
private EnumDescriptorProto.Builder builder;
private IRubyObject name;
private RubyModule module;
}

View File

@ -0,0 +1,270 @@
/*
* Protocol Buffers - Google's data interchange format
* Copyright 2014 Google Inc. All rights reserved.
* https://developers.google.com/protocol-buffers/
*
* 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.
*/
package com.google.protobuf.jruby;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.FileDescriptor;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
@JRubyClass(name = "FieldDescriptor")
public class RubyFieldDescriptor extends RubyObject {
public static void createRubyFieldDescriptor(Ruby runtime) {
RubyModule mProtobuf = runtime.getClassFromPath("Google::Protobuf");
RubyClass cFieldDescriptor = mProtobuf.defineClassUnder("FieldDescriptor", runtime.getObject(), new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new RubyFieldDescriptor(runtime, klazz);
}
});
cFieldDescriptor.defineAnnotatedMethods(RubyFieldDescriptor.class);
}
public RubyFieldDescriptor(Ruby runtime, RubyClass klazz) {
super(runtime, klazz);
}
/*
* call-seq:
* FieldDescriptor.default => default
*
* Returns this field's default, as a Ruby object, or nil if not yet set.
*/
// VALUE FieldDescriptor_default(VALUE _self) {
// DEFINE_SELF(FieldDescriptor, self, _self);
// return layout_get_default(self->fielddef);
// }
/*
* call-seq:
* FieldDescriptor.label => label
*
* Returns this field's label (i.e., plurality), as a Ruby symbol.
*
* Valid field labels are:
* :optional, :repeated
*/
@JRubyMethod(name = "label")
public IRubyObject getLabel(ThreadContext context) {
if (label == null) {
calculateLabel(context);
}
return label;
}
/*
* call-seq:
* FieldDescriptor.name => name
*
* Returns the name of this field as a Ruby String, or nil if it is not set.
*/
@JRubyMethod(name = "name")
public IRubyObject getName(ThreadContext context) {
return this.name;
}
/*
* call-seq:
* FieldDescriptor.subtype => message_or_enum_descriptor
*
* Returns the message or enum descriptor corresponding to this field's type if
* it is a message or enum field, respectively, or nil otherwise. Cannot be
* called *until* the containing message type is added to a pool (and thus
* resolved).
*/
@JRubyMethod(name = "subtype")
public IRubyObject getSubtype(ThreadContext context) {
if (subtype == null) {
calculateSubtype(context);
}
return subtype;
}
/*
* call-seq:
* FieldDescriptor.type => type
*
* Returns this field's type, as a Ruby symbol, or nil if not yet set.
*
* Valid field types are:
* :int32, :int64, :uint32, :uint64, :float, :double, :bool, :string,
* :bytes, :message.
*/
@JRubyMethod(name = "type")
public IRubyObject getType(ThreadContext context) {
return Utils.fieldTypeToRuby(context, descriptor.getType());
}
/*
* call-seq:
* FieldDescriptor.number => number
*
* Returns the tag number for this field.
*/
@JRubyMethod(name = "number")
public IRubyObject getNumber(ThreadContext context) {
return this.number;
}
/*
* call-seq:
* FieldDescriptor.submsg_name => submsg_name
*
* Returns the name of the message or enum type corresponding to this field, if
* it is a message or enum field (respectively), or nil otherwise. This type
* name will be resolved within the context of the pool to which the containing
* message type is added.
*/
// VALUE FieldDescriptor_submsg_name(VALUE _self) {
// DEFINE_SELF(FieldDescriptor, self, _self);
// switch (upb_fielddef_type(self->fielddef)) {
// case UPB_TYPE_ENUM:
// return rb_str_new2(
// upb_enumdef_fullname(upb_fielddef_enumsubdef(self->fielddef)));
// case UPB_TYPE_MESSAGE:
// return rb_str_new2(
// upb_msgdef_fullname(upb_fielddef_msgsubdef(self->fielddef)));
// default:
// return Qnil;
// }
// }
/*
* call-seq:
* FieldDescriptor.submsg_name = submsg_name
*
* Sets the name of the message or enum type corresponding to this field, if it
* is a message or enum field (respectively). This type name will be resolved
* within the context of the pool to which the containing message type is added.
* Cannot be called on field that are not of message or enum type, or on fields
* that are part of a message type already added to a pool.
*/
// @JRubyMethod(name = "submsg_name=")
// public IRubyObject setSubmsgName(ThreadContext context, IRubyObject name) {
// this.builder.setTypeName("." + Utils.escapeIdentifier(name.asJavaString()));
// return context.runtime.getNil();
// }
/*
* call-seq:
* FieldDescriptor.clear(message)
*
* Clears the field from the message if it's set.
*/
@JRubyMethod(name = "clear")
public IRubyObject clearValue(ThreadContext context, IRubyObject message) {
return ((RubyMessage) message).clearField(context, descriptor);
}
/*
* call-seq:
* FieldDescriptor.get(message) => value
*
* Returns the value set for this field on the given message. Raises an
* exception if message is of the wrong type.
*/
@JRubyMethod(name = "get")
public IRubyObject getValue(ThreadContext context, IRubyObject message) {
return ((RubyMessage) message).getField(context, descriptor);
}
/*
* call-seq:
* FieldDescriptor.has?(message) => boolean
*
* Returns whether the value is set on the given message. Raises an
* exception when calling for fields that do not have presence.
*/
@JRubyMethod(name = "has?")
public IRubyObject has(ThreadContext context, IRubyObject message) {
return ((RubyMessage) message).hasField(context, descriptor);
}
/*
* call-seq:
* FieldDescriptor.set(message, value)
*
* Sets the value corresponding to this field to the given value on the given
* message. Raises an exception if message is of the wrong type. Performs the
* ordinary type-checks for field setting.
*/
@JRubyMethod(name = "set")
public IRubyObject setValue(ThreadContext context, IRubyObject message, IRubyObject value) {
((RubyMessage) message).setField(context, descriptor, value);
return context.nil;
}
protected void setDescriptor(ThreadContext context, FieldDescriptor descriptor, RubyDescriptorPool pool) {
if (descriptor.isRequired() && descriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3) {
throw Utils.createTypeError(context, descriptor.getName() + " is labeled required but required fields are unsupported in proto3");
}
this.descriptor = descriptor;
this.name = context.runtime.newString(descriptor.getName());
this.pool = pool;
}
private void calculateLabel(ThreadContext context) {
if (descriptor.isRepeated()) {
this.label = context.runtime.newSymbol("repeated");
} else if (descriptor.isOptional()) {
this.label = context.runtime.newSymbol("optional");
} else {
this.label = context.nil;
}
}
private void calculateSubtype(ThreadContext context) {
FieldDescriptor.Type fdType = descriptor.getType();
if (fdType == FieldDescriptor.Type.MESSAGE) {
RubyString messageName = context.runtime.newString(descriptor.getMessageType().getFullName());
this.subtype = pool.lookup(context, messageName);
} else if (fdType == FieldDescriptor.Type.ENUM) {
RubyString enumName = context.runtime.newString(descriptor.getEnumType().getFullName());
this.subtype = pool.lookup(context, enumName);
} else {
this.subtype = context.nil;
}
}
private static final String DOT = ".";
private FieldDescriptor descriptor;
private IRubyObject name;
private IRubyObject label;
private IRubyObject number;
private IRubyObject subtype;
private RubyDescriptorPool pool;
}

View File

@ -0,0 +1,106 @@
/*
* Protocol Buffers - Google's data interchange format
* Copyright 2014 Google Inc. All rights reserved.
* https://developers.google.com/protocol-buffers/
*
* 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.
*/
package com.google.protobuf.jruby;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FileDescriptor;
import com.google.protobuf.Descriptors.FileDescriptor.Syntax.*;
import com.google.protobuf.Descriptors.GenericDescriptor;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
@JRubyClass(name = "FileDescriptor")
public class RubyFileDescriptor extends RubyObject {
public static void createRubyFileDescriptor(Ruby runtime) {
RubyModule mProtobuf = runtime.getClassFromPath("Google::Protobuf");
cFileDescriptor = mProtobuf.defineClassUnder("FileDescriptor", runtime.getObject(), new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new RubyFileDescriptor(runtime, klazz);
}
});
cFileDescriptor.defineAnnotatedMethods(RubyFileDescriptor.class);
}
public static RubyFileDescriptor getRubyFileDescriptor(ThreadContext context, GenericDescriptor descriptor) {
RubyFileDescriptor rfd = (RubyFileDescriptor) cFileDescriptor.newInstance(context, Block.NULL_BLOCK);
rfd.fileDescriptor = descriptor.getFile();
return rfd;
}
public RubyFileDescriptor(Ruby runtime, RubyClass klazz) {
super(runtime, klazz);
}
/*
* call-seq:
* FileDescriptor.name => name
*
* Returns the name of the file.
*/
@JRubyMethod(name = "name")
public IRubyObject getName(ThreadContext context) {
String name = fileDescriptor.getName();
return name == null ? context.nil : context.runtime.newString(name);
}
/*
* call-seq:
* FileDescriptor.syntax => syntax
*
* Returns this file descriptors syntax.
*
* Valid syntax versions are:
* :proto2 or :proto3.
*/
@JRubyMethod(name = "syntax")
public IRubyObject getSyntax(ThreadContext context) {
switch (fileDescriptor.getSyntax()) {
case PROTO2:
return context.runtime.newSymbol("proto2");
case PROTO3:
return context.runtime.newSymbol("proto3");
default:
return context.nil;
}
}
private static RubyClass cFileDescriptor;
private FileDescriptor fileDescriptor;
}

View File

@ -0,0 +1,474 @@
/*
* Protocol Buffers - Google's data interchange format
* Copyright 2014 Google Inc. All rights reserved.
* https://developers.google.com/protocol-buffers/
*
* 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.
*/
package com.google.protobuf.jruby;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.DynamicMessage;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@JRubyClass(name = "Map", include = "Enumerable")
public class RubyMap extends RubyObject {
public static void createRubyMap(Ruby runtime) {
RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
RubyClass cMap = protobuf.defineClassUnder("Map", runtime.getObject(), new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby ruby, RubyClass rubyClass) {
return new RubyMap(ruby, rubyClass);
}
});
cMap.includeModule(runtime.getEnumerable());
cMap.defineAnnotatedMethods(RubyMap.class);
}
public RubyMap(Ruby ruby, RubyClass rubyClass) {
super(ruby, rubyClass);
}
/*
* call-seq:
* Map.new(key_type, value_type, value_typeclass = nil, init_hashmap = {})
* => new map
*
* Allocates a new Map container. This constructor may be called with 2, 3, or 4
* arguments. The first two arguments are always present and are symbols (taking
* on the same values as field-type symbols in message descriptors) that
* indicate the type of the map key and value fields.
*
* The supported key types are: :int32, :int64, :uint32, :uint64, :fixed32,
* :fixed64, :sfixed32, :sfixed64, :sint32, :sint64, :bool, :string, :bytes.
*
* The supported value types are: :int32, :int64, :uint32, :uint64, :fixed32,
* :fixed64, :sfixed32, :sfixed64, :sint32, :sint64, :bool, :string, :bytes,
* :enum, :message.
*
* The third argument, value_typeclass, must be present if value_type is :enum
* or :message. As in RepeatedField#new, this argument must be a message class
* (for :message) or enum module (for :enum).
*
* The last argument, if present, provides initial content for map. Note that
* this may be an ordinary Ruby hashmap or another Map instance with identical
* key and value types. Also note that this argument may be present whether or
* not value_typeclass is present (and it is unambiguously separate from
* value_typeclass because value_typeclass's presence is strictly determined by
* value_type). The contents of this initial hashmap or Map instance are
* shallow-copied into the new Map: the original map is unmodified, but
* references to underlying objects will be shared if the value type is a
* message type.
*/
@JRubyMethod(required = 2, optional = 2)
public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
this.table = new HashMap<IRubyObject, IRubyObject>();
this.keyType = Utils.rubyToFieldType(args[0]);
this.valueType = Utils.rubyToFieldType(args[1]);
switch(keyType) {
case STRING:
case BYTES:
this.keyTypeIsString = true;
break;
case INT32:
case INT64:
case SINT32:
case SINT64:
case UINT32:
case UINT64:
case FIXED32:
case FIXED64:
case SFIXED32:
case SFIXED64:
case BOOL:
// These are OK.
break;
default:
throw context.runtime.newArgumentError("Invalid key type for map.");
}
int initValueArg = 2;
if (needTypeclass(this.valueType) && args.length > 2) {
this.valueTypeClass = args[2];
Utils.validateTypeClass(context, this.valueType, this.valueTypeClass);
initValueArg = 3;
} else {
this.valueTypeClass = context.runtime.getNilClass();
}
if (args.length > initValueArg) {
mergeIntoSelf(context, args[initValueArg]);
}
return this;
}
/*
* call-seq:
* Map.[]=(key, value) => value
*
* Inserts or overwrites the value at the given key with the given new value.
* Throws an exception if the key type is incorrect. Returns the new value that
* was just inserted.
*/
@JRubyMethod(name = "[]=")
public IRubyObject indexSet(ThreadContext context, IRubyObject key, IRubyObject value) {
checkFrozen();
/*
* String types for keys return a different error than
* other types for keys, so deal with them specifically first
*/
if (keyTypeIsString && !(key instanceof RubySymbol || key instanceof RubyString)) {
throw Utils.createTypeError(context, "Expected string for map key");
}
key = Utils.checkType(context, keyType, "key", key, (RubyModule) valueTypeClass);
value = Utils.checkType(context, valueType, "value", value, (RubyModule) valueTypeClass);
IRubyObject symbol;
if (valueType == FieldDescriptor.Type.ENUM &&
Utils.isRubyNum(value) &&
! (symbol = RubyEnum.lookup(context, valueTypeClass, value)).isNil()) {
value = symbol;
}
this.table.put(key, value);
return value;
}
/*
* call-seq:
* Map.[](key) => value
*
* Accesses the element at the given key. Throws an exception if the key type is
* incorrect. Returns nil when the key is not present in the map.
*/
@JRubyMethod(name = "[]")
public IRubyObject index(ThreadContext context, IRubyObject key) {
key = Utils.symToString(key);
return Helpers.nullToNil(table.get(key), context.nil);
}
/*
* call-seq:
* Map.==(other) => boolean
*
* Compares this map to another. Maps are equal if they have identical key sets,
* and for each key, the values in both maps compare equal. Elements are
* compared as per normal Ruby semantics, by calling their :== methods (or
* performing a more efficient comparison for primitive types).
*
* Maps with dissimilar key types or value types/typeclasses are never equal,
* even if value comparison (for example, between integers and floats) would
* have otherwise indicated that every element has equal value.
*/
@JRubyMethod(name = "==")
public IRubyObject eq(ThreadContext context, IRubyObject _other) {
if (_other instanceof RubyHash)
return singleLevelHash(context).op_equal(context, _other);
RubyMap other = (RubyMap) _other;
if (this == other) return context.runtime.getTrue();
if (!typeCompatible(other) || this.table.size() != other.table.size())
return context.runtime.getFalse();
for (IRubyObject key : table.keySet()) {
if (! other.table.containsKey(key))
return context.runtime.getFalse();
if (! other.table.get(key).equals(table.get(key)))
return context.runtime.getFalse();
}
return context.runtime.getTrue();
}
/*
* call-seq:
* Map.inspect => string
*
* Returns a string representing this map's elements. It will be formatted as
* "{key => value, key => value, ...}", with each key and value string
* representation computed by its own #inspect method.
*/
@JRubyMethod
public IRubyObject inspect() {
return singleLevelHash(getRuntime().getCurrentContext()).inspect();
}
/*
* call-seq:
* Map.hash => hash_value
*
* Returns a hash value based on this map's contents.
*/
@JRubyMethod
public IRubyObject hash(ThreadContext context) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
for (IRubyObject key : table.keySet()) {
digest.update((byte) key.hashCode());
digest.update((byte) table.get(key).hashCode());
}
return context.runtime.newFixnum(ByteBuffer.wrap(digest.digest()).getLong());
} catch (NoSuchAlgorithmException ignore) {
return context.runtime.newFixnum(System.identityHashCode(table));
}
}
/*
* call-seq:
* Map.keys => [list_of_keys]
*
* Returns the list of keys contained in the map, in unspecified order.
*/
@JRubyMethod
public IRubyObject keys(ThreadContext context) {
return RubyArray.newArray(context.runtime, table.keySet());
}
/*
* call-seq:
* Map.values => [list_of_values]
*
* Returns the list of values contained in the map, in unspecified order.
*/
@JRubyMethod
public IRubyObject values(ThreadContext context) {
return RubyArray.newArray(context.runtime, table.values());
}
/*
* call-seq:
* Map.clear
*
* Removes all entries from the map.
*/
@JRubyMethod
public IRubyObject clear(ThreadContext context) {
checkFrozen();
table.clear();
return context.nil;
}
/*
* call-seq:
* Map.each(&block)
*
* Invokes &block on each |key, value| pair in the map, in unspecified order.
* Note that Map also includes Enumerable; map thus acts like a normal Ruby
* sequence.
*/
@JRubyMethod
public IRubyObject each(ThreadContext context, Block block) {
for (IRubyObject key : table.keySet()) {
block.yieldSpecific(context, key, table.get(key));
}
return context.nil;
}
/*
* call-seq:
* Map.delete(key) => old_value
*
* Deletes the value at the given key, if any, returning either the old value or
* nil if none was present. Throws an exception if the key is of the wrong type.
*/
@JRubyMethod
public IRubyObject delete(ThreadContext context, IRubyObject key) {
checkFrozen();
return table.remove(key);
}
/*
* call-seq:
* Map.has_key?(key) => bool
*
* Returns true if the given key is present in the map. Throws an exception if
* the key has the wrong type.
*/
@JRubyMethod(name = "has_key?")
public IRubyObject hasKey(ThreadContext context, IRubyObject key) {
return this.table.containsKey(key) ? context.runtime.getTrue() : context.runtime.getFalse();
}
/*
* call-seq:
* Map.length
*
* Returns the number of entries (key-value pairs) in the map.
*/
@JRubyMethod(name = {"length", "size"})
public IRubyObject length(ThreadContext context) {
return context.runtime.newFixnum(this.table.size());
}
/*
* call-seq:
* Map.dup => new_map
*
* Duplicates this map with a shallow copy. References to all non-primitive
* element objects (e.g., submessages) are shared.
*/
@JRubyMethod
public IRubyObject dup(ThreadContext context) {
RubyMap newMap = newThisType(context);
for (Map.Entry<IRubyObject, IRubyObject> entry : table.entrySet()) {
newMap.table.put(entry.getKey(), entry.getValue());
}
return newMap;
}
@JRubyMethod(name = "to_h")
public RubyHash toHash(ThreadContext context) {
Map<IRubyObject, IRubyObject> mapForHash = new HashMap();
table.forEach((key, value) -> {
if (!value.isNil()) {
if (value.respondsTo("to_h")) {
value = Helpers.invoke(context, value, "to_h");
} else if (value.respondsTo("to_a")) {
value = Helpers.invoke(context, value, "to_a");
}
mapForHash.put(key, value);
}
});
return RubyHash.newHash(context.runtime, mapForHash, context.nil);
}
// Used by Google::Protobuf.deep_copy but not exposed directly.
protected IRubyObject deepCopy(ThreadContext context) {
RubyMap newMap = newThisType(context);
switch (valueType) {
case MESSAGE:
for (IRubyObject key : table.keySet()) {
RubyMessage message = (RubyMessage) table.get(key);
newMap.table.put(key.dup(), message.deepCopy(context));
}
break;
default:
for (IRubyObject key : table.keySet()) {
newMap.table.put(key.dup(), table.get(key).dup());
}
}
return newMap;
}
protected List<DynamicMessage> build(ThreadContext context, RubyDescriptor descriptor, int depth, int recursionLimit) {
List<DynamicMessage> list = new ArrayList<DynamicMessage>();
RubyClass rubyClass = (RubyClass) descriptor.msgclass(context);
FieldDescriptor keyField = descriptor.getField("key");
FieldDescriptor valueField = descriptor.getField("value");
for (IRubyObject key : table.keySet()) {
RubyMessage mapMessage = (RubyMessage) rubyClass.newInstance(context, Block.NULL_BLOCK);
mapMessage.setField(context, keyField, key);
mapMessage.setField(context, valueField, table.get(key));
list.add(mapMessage.build(context, depth + 1, recursionLimit));
}
return list;
}
protected RubyMap mergeIntoSelf(final ThreadContext context, IRubyObject hashmap) {
if (hashmap instanceof RubyHash) {
((RubyHash) hashmap).visitAll(context, new RubyHash.Visitor() {
@Override
public void visit(IRubyObject key, IRubyObject val) {
if (val instanceof RubyHash && !valueTypeClass.isNil()) {
val = ((RubyClass) valueTypeClass).newInstance(context, val, Block.NULL_BLOCK);
}
indexSet(context, key, val);
}
}, null);
} else if (hashmap instanceof RubyMap) {
RubyMap other = (RubyMap) hashmap;
if (!typeCompatible(other)) {
throw Utils.createTypeError(context, "Attempt to merge Map with mismatching types");
}
} else {
throw Utils.createTypeError(context, "Unknown type merging into Map");
}
return this;
}
protected boolean typeCompatible(RubyMap other) {
return this.keyType == other.keyType &&
this.valueType == other.valueType &&
this.valueTypeClass == other.valueTypeClass;
}
private RubyMap newThisType(ThreadContext context) {
RubyMap newMap;
if (needTypeclass(valueType)) {
newMap = (RubyMap) metaClass.newInstance(context,
Utils.fieldTypeToRuby(context, keyType),
Utils.fieldTypeToRuby(context, valueType),
valueTypeClass, Block.NULL_BLOCK);
} else {
newMap = (RubyMap) metaClass.newInstance(context,
Utils.fieldTypeToRuby(context, keyType),
Utils.fieldTypeToRuby(context, valueType),
Block.NULL_BLOCK);
}
newMap.table = new HashMap<IRubyObject, IRubyObject>();
return newMap;
}
/*
* toHash calls toHash on values, for some camparisons we only need
* a hash with the original objects still as values
*/
private RubyHash singleLevelHash(ThreadContext context) {
return RubyHash.newHash(context.runtime, table, context.nil);
}
private boolean needTypeclass(FieldDescriptor.Type type) {
switch(type) {
case MESSAGE:
case ENUM:
return true;
default:
return false;
}
}
private FieldDescriptor.Type keyType;
private FieldDescriptor.Type valueType;
private IRubyObject valueTypeClass;
private Map<IRubyObject, IRubyObject> table;
private boolean keyTypeIsString = false;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,86 @@
package com.google.protobuf.jruby;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.Descriptors.OneofDescriptor;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyModule;
import org.jruby.RubyObject;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@JRubyClass(name = "OneofDescriptor", include = "Enumerable")
public class RubyOneofDescriptor extends RubyObject {
public static void createRubyOneofDescriptor(Ruby runtime) {
RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
RubyClass cRubyOneofDescriptor = protobuf.defineClassUnder("OneofDescriptor", runtime.getObject(), new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby ruby, RubyClass rubyClass) {
return new RubyOneofDescriptor(ruby, rubyClass);
}
});
cRubyOneofDescriptor.defineAnnotatedMethods(RubyOneofDescriptor.class);
cRubyOneofDescriptor.includeModule(runtime.getEnumerable());
}
public RubyOneofDescriptor(Ruby ruby, RubyClass rubyClass) {
super(ruby, rubyClass);
fields = new ArrayList<RubyFieldDescriptor>();
}
/*
* call-seq:
* OneofDescriptor.name => name
*
* Returns the name of this oneof.
*/
@JRubyMethod(name = "name")
public IRubyObject getName(ThreadContext context) {
return name;
}
/*
* call-seq:
* OneofDescriptor.each(&block) => nil
*
* Iterates through fields in this oneof, yielding to the block on each one.
*/
@JRubyMethod
public IRubyObject each(ThreadContext context, Block block) {
for (RubyFieldDescriptor field : fields) {
block.yieldSpecific(context, field);
}
return context.nil;
}
protected Collection<RubyFieldDescriptor> getFields() {
return fields;
}
protected OneofDescriptor getDescriptor() {
return descriptor;
}
protected void setDescriptor(ThreadContext context, OneofDescriptor descriptor, Map<FieldDescriptor, RubyFieldDescriptor> fieldCache) {
this.descriptor = descriptor;
this.name = context.runtime.newString(descriptor.getName());
for (FieldDescriptor fd : descriptor.getFields()) {
fields.add(fieldCache.get(fd));
}
}
private IRubyObject name;
private List<RubyFieldDescriptor> fields;
private OneofDescriptor descriptor;
}

View File

@ -0,0 +1,82 @@
/*
* Protocol Buffers - Google's data interchange format
* Copyright 2014 Google Inc. All rights reserved.
* https://developers.google.com/protocol-buffers/
*
* 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.
*/
package com.google.protobuf.jruby;
import org.jruby.Ruby;
import org.jruby.RubyModule;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
@JRubyModule(name = "Protobuf")
public class RubyProtobuf {
public static void createProtobuf(Ruby runtime) {
RubyModule mGoogle = runtime.getModule("Google");
RubyModule mProtobuf = mGoogle.defineModuleUnder("Protobuf");
mProtobuf.defineAnnotatedMethods(RubyProtobuf.class);
RubyModule mInternal = mProtobuf.defineModuleUnder("Internal");
}
/*
* call-seq:
* Google::Protobuf.deep_copy(obj) => copy_of_obj
*
* Performs a deep copy of either a RepeatedField instance or a message object,
* recursively copying its members.
*/
@JRubyMethod(name = "deep_copy", meta = true)
public static IRubyObject deepCopy(ThreadContext context, IRubyObject self, IRubyObject message) {
if (message instanceof RubyMessage) {
return ((RubyMessage) message).deepCopy(context);
} else if (message instanceof RubyRepeatedField) {
return ((RubyRepeatedField) message).deepCopy(context);
} else {
return ((RubyMap) message).deepCopy(context);
}
}
/*
* call-seq:
* Google::Protobuf.discard_unknown(msg)
*
* Discard unknown fields in the given message object and recursively discard
* unknown fields in submessages.
*/
@JRubyMethod(name = "discard_unknown", meta = true)
public static IRubyObject discardUnknown(ThreadContext context, IRubyObject self, IRubyObject message) {
((RubyMessage) message).discardUnknownFields(context);
return context.nil;
}
}

View File

@ -0,0 +1,423 @@
/*
* Protocol Buffers - Google's data interchange format
* Copyright 2014 Google Inc. All rights reserved.
* https://developers.google.com/protocol-buffers/
*
* 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.
*/
package com.google.protobuf.jruby;
import com.google.protobuf.Descriptors.FieldDescriptor;
import org.jruby.*;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import java.util.Arrays;
@JRubyClass(name = "RepeatedClass", include = "Enumerable")
public class RubyRepeatedField extends RubyObject {
public static void createRubyRepeatedField(Ruby runtime) {
RubyModule mProtobuf = runtime.getClassFromPath("Google::Protobuf");
RubyClass cRepeatedField = mProtobuf.defineClassUnder("RepeatedField", runtime.getObject(),
new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
return new RubyRepeatedField(runtime, klazz);
}
});
cRepeatedField.defineAnnotatedMethods(RubyRepeatedField.class);
cRepeatedField.includeModule(runtime.getEnumerable());
}
public RubyRepeatedField(Ruby runtime, RubyClass klazz) {
super(runtime, klazz);
}
public RubyRepeatedField(Ruby runtime, RubyClass klazz, FieldDescriptor.Type fieldType, IRubyObject typeClass) {
this(runtime, klazz);
this.fieldType = fieldType;
this.storage = runtime.newArray();
this.typeClass = typeClass;
}
@JRubyMethod(required = 1, optional = 2)
public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
Ruby runtime = context.runtime;
this.storage = runtime.newArray();
IRubyObject ary = null;
if (!(args[0] instanceof RubySymbol)) {
throw runtime.newArgumentError("Expected Symbol for type name");
}
this.fieldType = Utils.rubyToFieldType(args[0]);
if (fieldType == FieldDescriptor.Type.MESSAGE
|| fieldType == FieldDescriptor.Type.ENUM) {
if (args.length < 2)
throw runtime.newArgumentError("Expected at least 2 arguments for message/enum");
typeClass = args[1];
if (args.length > 2)
ary = args[2];
Utils.validateTypeClass(context, fieldType, typeClass);
} else {
if (args.length > 2)
throw runtime.newArgumentError("Too many arguments: expected 1 or 2");
if (args.length > 1)
ary = args[1];
}
if (ary != null) {
RubyArray arr = ary.convertToArray();
for (int i = 0; i < arr.size(); i++) {
this.storage.add(arr.eltInternal(i));
}
}
return this;
}
/*
* call-seq:
* RepeatedField.[]=(index, value)
*
* Sets the element at the given index. On out-of-bounds assignments, extends
* the array and fills the hole (if any) with default values.
*/
@JRubyMethod(name = "[]=")
public IRubyObject indexSet(ThreadContext context, IRubyObject index, IRubyObject value) {
int arrIndex = normalizeArrayIndex(index);
value = Utils.checkType(context, fieldType, name, value, (RubyModule) typeClass);
IRubyObject defaultValue = defaultValue(context);
for (int i = this.storage.size(); i < arrIndex; i++) {
this.storage.set(i, defaultValue);
}
this.storage.set(arrIndex, value);
return context.runtime.getNil();
}
/*
* call-seq:
* RepeatedField.[](index) => value
*
* Accesses the element at the given index. Returns nil on out-of-bounds
*/
@JRubyMethod(required=1, optional=1, name = {"at", "[]"})
public IRubyObject index(ThreadContext context, IRubyObject[] args) {
if (args.length == 1){
IRubyObject arg = args[0];
if (Utils.isRubyNum(arg)) {
/* standard case */
int arrIndex = normalizeArrayIndex(arg);
if (arrIndex < 0 || arrIndex >= this.storage.size()) {
return context.runtime.getNil();
}
return this.storage.eltInternal(arrIndex);
} else if (arg instanceof RubyRange) {
RubyRange range = ((RubyRange) arg);
int beg = RubyNumeric.num2int(range.first(context));
int len = RubyNumeric.num2int(range.size(context));
if (len == 0) return context.runtime.newEmptyArray();
return this.storage.subseq(beg, len);
}
}
/* assume 2 arguments */
int beg = RubyNumeric.num2int(args[0]);
int len = RubyNumeric.num2int(args[1]);
if (beg < 0) {
beg += this.storage.size();
}
if (beg >= this.storage.size()) {
return context.runtime.getNil();
}
return this.storage.subseq(beg, len);
}
/*
* call-seq:
* RepeatedField.push(value)
*
* Adds a new element to the repeated field.
*/
@JRubyMethod(name = {"push", "<<"}, required = 1, rest = true)
public IRubyObject push(ThreadContext context, IRubyObject[] args) {
for (int i = 0; i < args.length; i++) {
IRubyObject val = args[i];
if (fieldType != FieldDescriptor.Type.MESSAGE || !val.isNil()) {
val = Utils.checkType(context, fieldType, name, val, (RubyModule) typeClass);
}
storage.add(val);
}
return this;
}
/*
* private Ruby method used by RepeatedField.pop
*/
@JRubyMethod(visibility = org.jruby.runtime.Visibility.PRIVATE)
public IRubyObject pop_one(ThreadContext context) {
IRubyObject ret = this.storage.last();
this.storage.remove(ret);
return ret;
}
/*
* call-seq:
* RepeatedField.replace(list)
*
* Replaces the contents of the repeated field with the given list of elements.
*/
@JRubyMethod
public IRubyObject replace(ThreadContext context, IRubyObject list) {
RubyArray arr = (RubyArray) list;
checkArrayElementType(context, arr);
this.storage = arr;
return this;
}
/*
* call-seq:
* RepeatedField.clear
*
* Clears (removes all elements from) this repeated field.
*/
@JRubyMethod
public IRubyObject clear(ThreadContext context) {
this.storage.clear();
return this;
}
/*
* call-seq:
* RepeatedField.length
*
* Returns the length of this repeated field.
*/
@JRubyMethod(name = {"length", "size"})
public IRubyObject length(ThreadContext context) {
return context.runtime.newFixnum(this.storage.size());
}
/*
* call-seq:
* RepeatedField.+(other) => repeated field
*
* Returns a new repeated field that contains the concatenated list of this
* repeated field's elements and other's elements. The other (second) list may
* be either another repeated field or a Ruby array.
*/
@JRubyMethod(name = {"+"})
public IRubyObject plus(ThreadContext context, IRubyObject list) {
RubyRepeatedField dup = (RubyRepeatedField) dup(context);
if (list instanceof RubyArray) {
checkArrayElementType(context, (RubyArray) list);
dup.storage.addAll((RubyArray) list);
} else {
RubyRepeatedField repeatedField = (RubyRepeatedField) list;
if (! fieldType.equals(repeatedField.fieldType) || (typeClass != null && !
typeClass.equals(repeatedField.typeClass)))
throw context.runtime.newArgumentError("Attempt to append RepeatedField with different element type.");
dup.storage.addAll((RubyArray) repeatedField.toArray(context));
}
return dup;
}
/*
* call-seq:
* RepeatedField.concat(other) => self
*
* concats the passed in array to self. Returns a Ruby array.
*/
@JRubyMethod
public IRubyObject concat(ThreadContext context, IRubyObject list) {
if (list instanceof RubyArray) {
checkArrayElementType(context, (RubyArray) list);
this.storage.addAll((RubyArray) list);
} else {
RubyRepeatedField repeatedField = (RubyRepeatedField) list;
if (! fieldType.equals(repeatedField.fieldType) || (typeClass != null && !
typeClass.equals(repeatedField.typeClass)))
throw context.runtime.newArgumentError("Attempt to append RepeatedField with different element type.");
this.storage.addAll((RubyArray) repeatedField.toArray(context));
}
return this;
}
/*
* call-seq:
* RepeatedField.hash => hash_value
*
* Returns a hash value computed from this repeated field's elements.
*/
@JRubyMethod
public IRubyObject hash(ThreadContext context) {
int hashCode = this.storage.hashCode();
return context.runtime.newFixnum(hashCode);
}
/*
* call-seq:
* RepeatedField.==(other) => boolean
*
* Compares this repeated field to another. Repeated fields are equal if their
* element types are equal, their lengths are equal, and each element is equal.
* Elements are compared as per normal Ruby semantics, by calling their :==
* methods (or performing a more efficient comparison for primitive types).
*/
@JRubyMethod(name = "==")
public IRubyObject eq(ThreadContext context, IRubyObject other) {
return this.toArray(context).op_equal(context, other);
}
/*
* call-seq:
* RepeatedField.each(&block)
*
* Invokes the block once for each element of the repeated field. RepeatedField
* also includes Enumerable; combined with this method, the repeated field thus
* acts like an ordinary Ruby sequence.
*/
@JRubyMethod
public IRubyObject each(ThreadContext context, Block block) {
this.storage.each(context, block);
return this;
}
@JRubyMethod(name = {"to_ary", "to_a"})
public IRubyObject toArray(ThreadContext context) {
return this.storage;
}
/*
* call-seq:
* RepeatedField.dup => repeated_field
*
* Duplicates this repeated field with a shallow copy. References to all
* non-primitive element objects (e.g., submessages) are shared.
*/
@JRubyMethod
public IRubyObject dup(ThreadContext context) {
RubyRepeatedField dup = new RubyRepeatedField(context.runtime, metaClass, fieldType, typeClass);
dup.push(context, storage.toJavaArray());
return dup;
}
@JRubyMethod
public IRubyObject inspect() {
return storage.inspect();
}
// Java API
protected IRubyObject get(int index) {
return this.storage.eltInternal(index);
}
protected RubyRepeatedField deepCopy(ThreadContext context) {
RubyRepeatedField copy = new RubyRepeatedField(context.runtime, metaClass, fieldType, typeClass);
for (int i = 0; i < size(); i++) {
IRubyObject value = storage.eltInternal(i);
if (fieldType == FieldDescriptor.Type.MESSAGE) {
copy.storage.add(((RubyMessage) value).deepCopy(context));
} else {
copy.storage.add(value);
}
}
return copy;
}
protected void setName(String name) {
this.name = name;
}
protected int size() {
return this.storage.size();
}
private IRubyObject defaultValue(ThreadContext context) {
SentinelOuterClass.Sentinel sentinel = SentinelOuterClass.Sentinel.getDefaultInstance();
Object value;
switch (fieldType) {
case INT32:
value = sentinel.getDefaultInt32();
break;
case INT64:
value = sentinel.getDefaultInt64();
break;
case UINT32:
value = sentinel.getDefaultUnit32();
break;
case UINT64:
value = sentinel.getDefaultUint64();
break;
case FLOAT:
value = sentinel.getDefaultFloat();
break;
case DOUBLE:
value = sentinel.getDefaultDouble();
break;
case BOOL:
value = sentinel.getDefaultBool();
break;
case BYTES:
value = sentinel.getDefaultBytes();
break;
case STRING:
value = sentinel.getDefaultString();
break;
case ENUM:
IRubyObject defaultEnumLoc = context.runtime.newFixnum(0);
return RubyEnum.lookup(context, typeClass, defaultEnumLoc);
default:
return context.runtime.getNil();
}
return Utils.wrapPrimaryValue(context, fieldType, value);
}
private void checkArrayElementType(ThreadContext context, RubyArray arr) {
for (int i = 0; i < arr.getLength(); i++) {
Utils.checkType(context, fieldType, name, arr.eltInternal(i), (RubyModule) typeClass);
}
}
private int normalizeArrayIndex(IRubyObject index) {
int arrIndex = RubyNumeric.num2int(index);
int arrSize = this.storage.size();
if (arrIndex < 0 && arrSize > 0) {
arrIndex = arrSize + arrIndex;
}
return arrIndex;
}
private FieldDescriptor.Type fieldType;
private IRubyObject typeClass;
private RubyArray storage;
private String name;
}

View File

@ -0,0 +1,776 @@
/*
* Protocol Buffers - Google's data interchange format
* Copyright 2014 Google Inc. All rights reserved.
* https://developers.google.com/protocol-buffers/
*
* 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.
*/
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: sentinel.proto
package com.google.protobuf.jruby;
public final class SentinelOuterClass {
private SentinelOuterClass() {}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistry registry) {
}
public interface SentinelOrBuilder extends
// @@protoc_insertion_point(interface_extends:com.google.protobuf.jruby.Sentinel)
com.google.protobuf.MessageOrBuilder {
/**
* <code>optional int32 default_int32 = 1;</code>
*/
int getDefaultInt32();
/**
* <code>optional int64 default_int64 = 2;</code>
*/
long getDefaultInt64();
/**
* <code>optional uint32 default_unit32 = 3;</code>
*/
int getDefaultUnit32();
/**
* <code>optional uint64 default_uint64 = 4;</code>
*/
long getDefaultUint64();
/**
* <code>optional string default_string = 5;</code>
*/
java.lang.String getDefaultString();
/**
* <code>optional string default_string = 5;</code>
*/
com.google.protobuf.ByteString
getDefaultStringBytes();
/**
* <code>optional bool default_bool = 6;</code>
*/
boolean getDefaultBool();
/**
* <code>optional float default_float = 7;</code>
*/
float getDefaultFloat();
/**
* <code>optional double default_double = 8;</code>
*/
double getDefaultDouble();
/**
* <code>optional bytes default_bytes = 9;</code>
*/
com.google.protobuf.ByteString getDefaultBytes();
}
/**
* Protobuf type {@code com.google.protobuf.jruby.Sentinel}
*/
public static final class Sentinel extends
com.google.protobuf.GeneratedMessage implements
// @@protoc_insertion_point(message_implements:com.google.protobuf.jruby.Sentinel)
SentinelOrBuilder {
// Use Sentinel.newBuilder() to construct.
private Sentinel(com.google.protobuf.GeneratedMessage.Builder builder) {
super(builder);
}
private Sentinel() {
defaultInt32_ = 0;
defaultInt64_ = 0L;
defaultUnit32_ = 0;
defaultUint64_ = 0L;
defaultString_ = "";
defaultBool_ = false;
defaultFloat_ = 0F;
defaultDouble_ = 0D;
defaultBytes_ = com.google.protobuf.ByteString.EMPTY;
}
@java.lang.Override
public final com.google.protobuf.UnknownFieldSet
getUnknownFields() {
return com.google.protobuf.UnknownFieldSet.getDefaultInstance();
}
public static final com.google.protobuf.Descriptors.Descriptor
getDescriptor() {
return com.google.protobuf.jruby.SentinelOuterClass.internal_static_com_google_protobuf_jruby_Sentinel_descriptor;
}
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
internalGetFieldAccessorTable() {
return com.google.protobuf.jruby.SentinelOuterClass.internal_static_com_google_protobuf_jruby_Sentinel_fieldAccessorTable
.ensureFieldAccessorsInitialized(
com.google.protobuf.jruby.SentinelOuterClass.Sentinel.class, com.google.protobuf.jruby.SentinelOuterClass.Sentinel.Builder.class);
}
public static final com.google.protobuf.Parser<Sentinel> PARSER =
new com.google.protobuf.AbstractParser<Sentinel>() {
public Sentinel parsePartialFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
Builder builder = newBuilder();
try {
builder.mergeFrom(input, extensionRegistry);
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
throw e.setUnfinishedMessage(builder.buildPartial());
} catch (java.io.IOException e) {
throw new com.google.protobuf.InvalidProtocolBufferException(
e.getMessage()).setUnfinishedMessage(builder.buildPartial());
}
return builder.buildPartial();
}
};
@java.lang.Override
public com.google.protobuf.Parser<Sentinel> getParserForType() {
return PARSER;
}
public static final int DEFAULT_INT32_FIELD_NUMBER = 1;
private int defaultInt32_;
/**
* <code>optional int32 default_int32 = 1;</code>
*/
public int getDefaultInt32() {
return defaultInt32_;
}
public static final int DEFAULT_INT64_FIELD_NUMBER = 2;
private long defaultInt64_;
/**
* <code>optional int64 default_int64 = 2;</code>
*/
public long getDefaultInt64() {
return defaultInt64_;
}
public static final int DEFAULT_UNIT32_FIELD_NUMBER = 3;
private int defaultUnit32_;
/**
* <code>optional uint32 default_unit32 = 3;</code>
*/
public int getDefaultUnit32() {
return defaultUnit32_;
}
public static final int DEFAULT_UINT64_FIELD_NUMBER = 4;
private long defaultUint64_;
/**
* <code>optional uint64 default_uint64 = 4;</code>
*/
public long getDefaultUint64() {
return defaultUint64_;
}
public static final int DEFAULT_STRING_FIELD_NUMBER = 5;
private java.lang.Object defaultString_;
/**
* <code>optional string default_string = 5;</code>
*/
public java.lang.String getDefaultString() {
java.lang.Object ref = defaultString_;
if (ref instanceof java.lang.String) {
return (java.lang.String) ref;
} else {
com.google.protobuf.ByteString bs =
(com.google.protobuf.ByteString) ref;
java.lang.String s = bs.toStringUtf8();
if (bs.isValidUtf8()) {
defaultString_ = s;
}
return s;
}
}
/**
* <code>optional string default_string = 5;</code>
*/
public com.google.protobuf.ByteString
getDefaultStringBytes() {
java.lang.Object ref = defaultString_;
if (ref instanceof java.lang.String) {
com.google.protobuf.ByteString b =
com.google.protobuf.ByteString.copyFromUtf8(
(java.lang.String) ref);
defaultString_ = b;
return b;
} else {
return (com.google.protobuf.ByteString) ref;
}
}
public static final int DEFAULT_BOOL_FIELD_NUMBER = 6;
private boolean defaultBool_;
/**
* <code>optional bool default_bool = 6;</code>
*/
public boolean getDefaultBool() {
return defaultBool_;
}
public static final int DEFAULT_FLOAT_FIELD_NUMBER = 7;
private float defaultFloat_;
/**
* <code>optional float default_float = 7;</code>
*/
public float getDefaultFloat() {
return defaultFloat_;
}
public static final int DEFAULT_DOUBLE_FIELD_NUMBER = 8;
private double defaultDouble_;
/**
* <code>optional double default_double = 8;</code>
*/
public double getDefaultDouble() {
return defaultDouble_;
}
public static final int DEFAULT_BYTES_FIELD_NUMBER = 9;
private com.google.protobuf.ByteString defaultBytes_;
/**
* <code>optional bytes default_bytes = 9;</code>
*/
public com.google.protobuf.ByteString getDefaultBytes() {
return defaultBytes_;
}
public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom(
com.google.protobuf.ByteString data)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data);
}
public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom(
com.google.protobuf.ByteString data,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data, extensionRegistry);
}
public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom(byte[] data)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data);
}
public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom(
byte[] data,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data, extensionRegistry);
}
public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom(java.io.InputStream input)
throws java.io.IOException {
return PARSER.parseFrom(input);
}
public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom(
java.io.InputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
return PARSER.parseFrom(input, extensionRegistry);
}
public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseDelimitedFrom(java.io.InputStream input)
throws java.io.IOException {
return PARSER.parseDelimitedFrom(input);
}
public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseDelimitedFrom(
java.io.InputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
return PARSER.parseDelimitedFrom(input, extensionRegistry);
}
public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom(
com.google.protobuf.CodedInputStream input)
throws java.io.IOException {
return PARSER.parseFrom(input);
}
public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
return PARSER.parseFrom(input, extensionRegistry);
}
public static Builder newBuilder() { return new Builder(); }
public Builder newBuilderForType() { return newBuilder(); }
public static Builder newBuilder(com.google.protobuf.jruby.SentinelOuterClass.Sentinel prototype) {
return newBuilder().mergeFrom(prototype);
}
public Builder toBuilder() { return newBuilder(this); }
@java.lang.Override
protected Builder newBuilderForType(
com.google.protobuf.GeneratedMessage.BuilderParent parent) {
Builder builder = new Builder(parent);
return builder;
}
/**
* Protobuf type {@code com.google.protobuf.jruby.Sentinel}
*/
public static final class Builder extends
com.google.protobuf.GeneratedMessage.Builder<Builder> implements
// @@protoc_insertion_point(builder_implements:com.google.protobuf.jruby.Sentinel)
com.google.protobuf.jruby.SentinelOuterClass.SentinelOrBuilder {
public static final com.google.protobuf.Descriptors.Descriptor
getDescriptor() {
return com.google.protobuf.jruby.SentinelOuterClass.internal_static_com_google_protobuf_jruby_Sentinel_descriptor;
}
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
internalGetFieldAccessorTable() {
return com.google.protobuf.jruby.SentinelOuterClass.internal_static_com_google_protobuf_jruby_Sentinel_fieldAccessorTable
.ensureFieldAccessorsInitialized(
com.google.protobuf.jruby.SentinelOuterClass.Sentinel.class, com.google.protobuf.jruby.SentinelOuterClass.Sentinel.Builder.class);
}
// Construct using com.google.protobuf.jruby.SentinelOuterClass.Sentinel.newBuilder()
private Builder() {
maybeForceBuilderInitialization();
}
private Builder(
com.google.protobuf.GeneratedMessage.BuilderParent parent) {
super(parent);
maybeForceBuilderInitialization();
}
private void maybeForceBuilderInitialization() {
if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
}
}
public Builder clear() {
super.clear();
defaultInt32_ = 0;
defaultInt64_ = 0L;
defaultUnit32_ = 0;
defaultUint64_ = 0L;
defaultString_ = "";
defaultBool_ = false;
defaultFloat_ = 0F;
defaultDouble_ = 0D;
defaultBytes_ = com.google.protobuf.ByteString.EMPTY;
return this;
}
public com.google.protobuf.Descriptors.Descriptor
getDescriptorForType() {
return com.google.protobuf.jruby.SentinelOuterClass.internal_static_com_google_protobuf_jruby_Sentinel_descriptor;
}
public com.google.protobuf.jruby.SentinelOuterClass.Sentinel getDefaultInstanceForType() {
return com.google.protobuf.jruby.SentinelOuterClass.Sentinel.getDefaultInstance();
}
public com.google.protobuf.jruby.SentinelOuterClass.Sentinel build() {
com.google.protobuf.jruby.SentinelOuterClass.Sentinel result = buildPartial();
if (!result.isInitialized()) {
throw newUninitializedMessageException(result);
}
return result;
}
public com.google.protobuf.jruby.SentinelOuterClass.Sentinel buildPartial() {
com.google.protobuf.jruby.SentinelOuterClass.Sentinel result = new com.google.protobuf.jruby.SentinelOuterClass.Sentinel(this);
result.defaultInt32_ = defaultInt32_;
result.defaultInt64_ = defaultInt64_;
result.defaultUnit32_ = defaultUnit32_;
result.defaultUint64_ = defaultUint64_;
result.defaultString_ = defaultString_;
result.defaultBool_ = defaultBool_;
result.defaultFloat_ = defaultFloat_;
result.defaultDouble_ = defaultDouble_;
result.defaultBytes_ = defaultBytes_;
onBuilt();
return result;
}
private int defaultInt32_ ;
/**
* <code>optional int32 default_int32 = 1;</code>
*/
public int getDefaultInt32() {
return defaultInt32_;
}
/**
* <code>optional int32 default_int32 = 1;</code>
*/
public Builder setDefaultInt32(int value) {
defaultInt32_ = value;
onChanged();
return this;
}
/**
* <code>optional int32 default_int32 = 1;</code>
*/
public Builder clearDefaultInt32() {
defaultInt32_ = 0;
onChanged();
return this;
}
private long defaultInt64_ ;
/**
* <code>optional int64 default_int64 = 2;</code>
*/
public long getDefaultInt64() {
return defaultInt64_;
}
/**
* <code>optional int64 default_int64 = 2;</code>
*/
public Builder setDefaultInt64(long value) {
defaultInt64_ = value;
onChanged();
return this;
}
/**
* <code>optional int64 default_int64 = 2;</code>
*/
public Builder clearDefaultInt64() {
defaultInt64_ = 0L;
onChanged();
return this;
}
private int defaultUnit32_ ;
/**
* <code>optional uint32 default_unit32 = 3;</code>
*/
public int getDefaultUnit32() {
return defaultUnit32_;
}
/**
* <code>optional uint32 default_unit32 = 3;</code>
*/
public Builder setDefaultUnit32(int value) {
defaultUnit32_ = value;
onChanged();
return this;
}
/**
* <code>optional uint32 default_unit32 = 3;</code>
*/
public Builder clearDefaultUnit32() {
defaultUnit32_ = 0;
onChanged();
return this;
}
private long defaultUint64_ ;
/**
* <code>optional uint64 default_uint64 = 4;</code>
*/
public long getDefaultUint64() {
return defaultUint64_;
}
/**
* <code>optional uint64 default_uint64 = 4;</code>
*/
public Builder setDefaultUint64(long value) {
defaultUint64_ = value;
onChanged();
return this;
}
/**
* <code>optional uint64 default_uint64 = 4;</code>
*/
public Builder clearDefaultUint64() {
defaultUint64_ = 0L;
onChanged();
return this;
}
private java.lang.Object defaultString_ = "";
/**
* <code>optional string default_string = 5;</code>
*/
public java.lang.String getDefaultString() {
java.lang.Object ref = defaultString_;
if (!(ref instanceof java.lang.String)) {
com.google.protobuf.ByteString bs =
(com.google.protobuf.ByteString) ref;
java.lang.String s = bs.toStringUtf8();
if (bs.isValidUtf8()) {
defaultString_ = s;
}
return s;
} else {
return (java.lang.String) ref;
}
}
/**
* <code>optional string default_string = 5;</code>
*/
public com.google.protobuf.ByteString
getDefaultStringBytes() {
java.lang.Object ref = defaultString_;
if (ref instanceof String) {
com.google.protobuf.ByteString b =
com.google.protobuf.ByteString.copyFromUtf8(
(java.lang.String) ref);
defaultString_ = b;
return b;
} else {
return (com.google.protobuf.ByteString) ref;
}
}
/**
* <code>optional string default_string = 5;</code>
*/
public Builder setDefaultString(
java.lang.String value) {
if (value == null) {
throw new NullPointerException();
}
defaultString_ = value;
onChanged();
return this;
}
/**
* <code>optional string default_string = 5;</code>
*/
public Builder clearDefaultString() {
defaultString_ = getDefaultInstance().getDefaultString();
onChanged();
return this;
}
/**
* <code>optional string default_string = 5;</code>
*/
public Builder setDefaultStringBytes(
com.google.protobuf.ByteString value) {
if (value == null) {
throw new NullPointerException();
}
defaultString_ = value;
onChanged();
return this;
}
private boolean defaultBool_ ;
/**
* <code>optional bool default_bool = 6;</code>
*/
public boolean getDefaultBool() {
return defaultBool_;
}
/**
* <code>optional bool default_bool = 6;</code>
*/
public Builder setDefaultBool(boolean value) {
defaultBool_ = value;
onChanged();
return this;
}
/**
* <code>optional bool default_bool = 6;</code>
*/
public Builder clearDefaultBool() {
defaultBool_ = false;
onChanged();
return this;
}
private float defaultFloat_ ;
/**
* <code>optional float default_float = 7;</code>
*/
public float getDefaultFloat() {
return defaultFloat_;
}
/**
* <code>optional float default_float = 7;</code>
*/
public Builder setDefaultFloat(float value) {
defaultFloat_ = value;
onChanged();
return this;
}
/**
* <code>optional float default_float = 7;</code>
*/
public Builder clearDefaultFloat() {
defaultFloat_ = 0F;
onChanged();
return this;
}
private double defaultDouble_ ;
/**
* <code>optional double default_double = 8;</code>
*/
public double getDefaultDouble() {
return defaultDouble_;
}
/**
* <code>optional double default_double = 8;</code>
*/
public Builder setDefaultDouble(double value) {
defaultDouble_ = value;
onChanged();
return this;
}
/**
* <code>optional double default_double = 8;</code>
*/
public Builder clearDefaultDouble() {
defaultDouble_ = 0D;
onChanged();
return this;
}
private com.google.protobuf.ByteString defaultBytes_ = com.google.protobuf.ByteString.EMPTY;
/**
* <code>optional bytes default_bytes = 9;</code>
*/
public com.google.protobuf.ByteString getDefaultBytes() {
return defaultBytes_;
}
/**
* <code>optional bytes default_bytes = 9;</code>
*/
public Builder setDefaultBytes(com.google.protobuf.ByteString value) {
if (value == null) {
throw new NullPointerException();
}
defaultBytes_ = value;
onChanged();
return this;
}
/**
* <code>optional bytes default_bytes = 9;</code>
*/
public Builder clearDefaultBytes() {
defaultBytes_ = getDefaultInstance().getDefaultBytes();
onChanged();
return this;
}
public final Builder setUnknownFields(
final com.google.protobuf.UnknownFieldSet unknownFields) {
return this;
}
public final Builder mergeUnknownFields(
final com.google.protobuf.UnknownFieldSet unknownFields) {
return this;
}
// @@protoc_insertion_point(builder_scope:com.google.protobuf.jruby.Sentinel)
}
// @@protoc_insertion_point(class_scope:com.google.protobuf.jruby.Sentinel)
private static final com.google.protobuf.jruby.SentinelOuterClass.Sentinel defaultInstance;static {
defaultInstance = new com.google.protobuf.jruby.SentinelOuterClass.Sentinel();
}
public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel getDefaultInstance() {
return defaultInstance;
}
public com.google.protobuf.jruby.SentinelOuterClass.Sentinel getDefaultInstanceForType() {
return defaultInstance;
}
}
private static final com.google.protobuf.Descriptors.Descriptor
internal_static_com_google_protobuf_jruby_Sentinel_descriptor;
private static
com.google.protobuf.GeneratedMessage.FieldAccessorTable
internal_static_com_google_protobuf_jruby_Sentinel_fieldAccessorTable;
public static com.google.protobuf.Descriptors.FileDescriptor
getDescriptor() {
return descriptor;
}
private static com.google.protobuf.Descriptors.FileDescriptor
descriptor;
static {
java.lang.String[] descriptorData = {
"\n\016sentinel.proto\022\031com.google.protobuf.jr" +
"uby\"\334\001\n\010Sentinel\022\025\n\rdefault_int32\030\001 \001(\005\022" +
"\025\n\rdefault_int64\030\002 \001(\003\022\026\n\016default_unit32" +
"\030\003 \001(\r\022\026\n\016default_uint64\030\004 \001(\004\022\026\n\016defaul" +
"t_string\030\005 \001(\t\022\024\n\014default_bool\030\006 \001(\010\022\025\n\r" +
"default_float\030\007 \001(\002\022\026\n\016default_double\030\010 " +
"\001(\001\022\025\n\rdefault_bytes\030\t \001(\014B\002H\002b\006proto3"
};
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor. InternalDescriptorAssigner() {
public com.google.protobuf.ExtensionRegistry assignDescriptors(
com.google.protobuf.Descriptors.FileDescriptor root) {
descriptor = root;
return null;
}
};
com.google.protobuf.Descriptors.FileDescriptor
.internalBuildGeneratedFileFrom(descriptorData,
new com.google.protobuf.Descriptors.FileDescriptor[] {
}, assigner);
internal_static_com_google_protobuf_jruby_Sentinel_descriptor =
getDescriptor().getMessageTypes().get(0);
internal_static_com_google_protobuf_jruby_Sentinel_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_com_google_protobuf_jruby_Sentinel_descriptor,
new java.lang.String[] { "DefaultInt32", "DefaultInt64", "DefaultUnit32", "DefaultUint64", "DefaultString", "DefaultBool", "DefaultFloat", "DefaultDouble", "DefaultBytes", });
}
// @@protoc_insertion_point(outer_class_scope)
}

View File

@ -0,0 +1,345 @@
/*
* Protocol Buffers - Google's data interchange format
* Copyright 2014 Google Inc. All rights reserved.
* https://developers.google.com/protocol-buffers/
*
* 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.
*/
package com.google.protobuf.jruby;
import com.google.protobuf.ByteString;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import com.google.protobuf.Descriptors.FieldDescriptor;
import org.jcodings.specific.ASCIIEncoding;
import org.jruby.*;
import org.jruby.exceptions.RaiseException;
import org.jruby.ext.bigdecimal.RubyBigDecimal;
import org.jruby.runtime.Block;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import java.math.BigInteger;
public class Utils {
public static FieldDescriptor.Type rubyToFieldType(IRubyObject typeClass) {
return FieldDescriptor.Type.valueOf(typeClass.asJavaString().toUpperCase());
}
public static IRubyObject fieldTypeToRuby(ThreadContext context, FieldDescriptor.Type type) {
return fieldTypeToRuby(context, type.name());
}
public static IRubyObject fieldTypeToRuby(ThreadContext context, FieldDescriptorProto.Type type) {
return fieldTypeToRuby(context, type.name());
}
private static IRubyObject fieldTypeToRuby(ThreadContext context, String typeName) {
return context.runtime.newSymbol(typeName.replace("TYPE_", "").toLowerCase());
}
public static IRubyObject checkType(ThreadContext context, FieldDescriptor.Type fieldType,
String fieldName, IRubyObject value, RubyModule typeClass) {
Ruby runtime = context.runtime;
switch(fieldType) {
case SFIXED32:
case SFIXED64:
case FIXED64:
case SINT64:
case SINT32:
case FIXED32:
case INT32:
case INT64:
case UINT32:
case UINT64:
if (!isRubyNum(value))
throw createExpectedTypeError(context, "number", "integral", fieldName, value);
if (value instanceof RubyFloat) {
double doubleVal = RubyNumeric.num2dbl(value);
if (Math.floor(doubleVal) != doubleVal) {
throw runtime.newRangeError("Non-integral floating point value assigned to integer field '" + fieldName + "' (given " + value.getMetaClass() + ").");
}
}
if (fieldType == FieldDescriptor.Type.UINT32 || fieldType == FieldDescriptor.Type.UINT64 ||
fieldType == FieldDescriptor.Type.FIXED32 || fieldType == FieldDescriptor.Type.FIXED64) {
if (((RubyNumeric) value).isNegative()) {
throw runtime.newRangeError("Assigning negative value to unsigned integer field '" + fieldName + "' (given " + value.getMetaClass() + ").");
}
}
switch(fieldType) {
case INT32:
RubyNumeric.num2int(value);
break;
case UINT32:
case FIXED32:
num2uint(value);
break;
case UINT64:
case FIXED64:
num2ulong(context.runtime, value);
break;
default:
RubyNumeric.num2long(value);
break;
}
break;
case FLOAT:
if (!isRubyNum(value))
throw createExpectedTypeError(context, "number", "float", fieldName, value);
break;
case DOUBLE:
if (!isRubyNum(value))
throw createExpectedTypeError(context, "number", "double", fieldName, value);
break;
case BOOL:
if (!(value instanceof RubyBoolean))
throw createInvalidTypeError(context, "boolean", fieldName, value);
break;
case BYTES:
value = validateAndEncodeString(context, "bytes", fieldName, value, "Encoding::ASCII_8BIT");
break;
case STRING:
value = validateAndEncodeString(context, "string", fieldName, symToString(value), "Encoding::UTF_8");
break;
case MESSAGE:
if (value.getMetaClass() != typeClass) {
// See if we can convert the value before flagging it as invalid
String className = typeClass.getName();
if (className.equals("Google::Protobuf::Timestamp") && value instanceof RubyTime) {
RubyTime rt = (RubyTime) value;
RubyHash timestampArgs =
Helpers.constructHash(runtime,
runtime.newString("nanos"), rt.nsec(), false,
runtime.newString("seconds"), rt.to_i(), false);
return ((RubyClass) typeClass).newInstance(context, timestampArgs, Block.NULL_BLOCK);
} else if (className.equals("Google::Protobuf::Duration") && value instanceof RubyNumeric) {
IRubyObject seconds;
if (value instanceof RubyFloat) {
seconds = ((RubyFloat) value).truncate(context);
} else if (value instanceof RubyRational) {
seconds = ((RubyRational) value).to_i(context);
} else if (value instanceof RubyBigDecimal) {
seconds = ((RubyBigDecimal) value).to_int(context);
} else {
seconds = ((RubyInteger) value).to_i();
}
IRubyObject nanos = ((RubyNumeric) value).remainder(context, RubyFixnum.one(runtime));
if (nanos instanceof RubyFloat) {
nanos = ((RubyFloat) nanos).op_mul(context, 1000000000);
} else if (nanos instanceof RubyRational) {
nanos = ((RubyRational) nanos).op_mul(context, runtime.newFixnum(1000000000));
} else if (nanos instanceof RubyBigDecimal) {
nanos = ((RubyBigDecimal) nanos).op_mul(context, runtime.newFixnum(1000000000));
} else {
nanos = ((RubyInteger) nanos).op_mul(context, 1000000000);
}
RubyHash durationArgs =
Helpers.constructHash(runtime,
runtime.newString("nanos"), ((RubyNumeric) nanos).round(context), false,
runtime.newString("seconds"), seconds, false);
return ((RubyClass) typeClass).newInstance(context, durationArgs, Block.NULL_BLOCK);
}
// Not able to convert so flag as invalid
throw createTypeError(context, "Invalid type " + value.getMetaClass() + " to assign to submessage field '" + fieldName + "'.");
}
break;
case ENUM:
boolean isValid = ((RubyEnumDescriptor) typeClass.getInstanceVariable(DESCRIPTOR_INSTANCE_VAR)).isValidValue(context, value);
if (!isValid) {
throw runtime.newRangeError("Unknown symbol value for enum field '" + fieldName + "'.");
}
break;
default:
break;
}
return value;
}
public static IRubyObject wrapPrimaryValue(ThreadContext context, FieldDescriptor.Type fieldType, Object value) {
return wrapPrimaryValue(context, fieldType, value, false);
}
public static IRubyObject wrapPrimaryValue(ThreadContext context, FieldDescriptor.Type fieldType, Object value, boolean encodeBytes) {
Ruby runtime = context.runtime;
switch (fieldType) {
case INT32:
case SFIXED32:
case SINT32:
return runtime.newFixnum((Integer) value);
case SFIXED64:
case SINT64:
case INT64:
return runtime.newFixnum((Long) value);
case FIXED32:
case UINT32:
return runtime.newFixnum(((Integer) value) & (-1l >>> 32));
case FIXED64:
case UINT64:
long ret = (Long) value;
return ret >= 0 ? runtime.newFixnum(ret) :
RubyBignum.newBignum(runtime, UINT64_COMPLEMENTARY.add(new BigInteger(ret + "")));
case FLOAT:
return runtime.newFloat((Float) value);
case DOUBLE:
return runtime.newFloat((Double) value);
case BOOL:
return (Boolean) value ? runtime.getTrue() : runtime.getFalse();
case BYTES: {
IRubyObject wrapped = encodeBytes ?
RubyString.newString(runtime, ((ByteString) value).toStringUtf8(), ASCIIEncoding.INSTANCE) :
RubyString.newString(runtime, ((ByteString) value).toByteArray());
wrapped.setFrozen(true);
return wrapped;
}
case STRING: {
IRubyObject wrapped = runtime.newString(value.toString());
wrapped.setFrozen(true);
return wrapped;
}
default:
return runtime.getNil();
}
}
public static int num2uint(IRubyObject value) {
long longVal = RubyNumeric.num2long(value);
if (longVal > UINT_MAX)
throw value.getRuntime().newRangeError("Integer " + longVal + " too big to convert to 'unsigned int'");
long num = longVal;
if (num > Integer.MAX_VALUE || num < Integer.MIN_VALUE)
// encode to UINT32
num = (-longVal ^ (-1l >>> 32) ) + 1;
RubyNumeric.checkInt(value, num);
return (int) num;
}
public static long num2ulong(Ruby runtime, IRubyObject value) {
if (value instanceof RubyFloat) {
RubyBignum bignum = RubyBignum.newBignum(runtime, ((RubyFloat) value).getDoubleValue());
return RubyBignum.big2ulong(bignum);
} else if (value instanceof RubyBignum) {
return RubyBignum.big2ulong((RubyBignum) value);
} else {
return RubyNumeric.num2long(value);
}
}
/*
* Helper to make it easier to support symbols being passed instead of strings
*/
public static IRubyObject symToString(IRubyObject sym) {
if (sym instanceof RubySymbol) {
return ((RubySymbol) sym).id2name();
}
return sym;
}
public static void checkNameAvailability(ThreadContext context, String name) {
if (context.runtime.getObject().getConstantAt(name) != null)
throw context.runtime.newNameError(name + " is already defined", name);
}
public static boolean isMapEntry(FieldDescriptor fieldDescriptor) {
return fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE &&
fieldDescriptor.isRepeated() &&
fieldDescriptor.getMessageType().getOptions().getMapEntry();
}
public static RaiseException createTypeError(ThreadContext context, String message) {
if (cTypeError == null) {
cTypeError = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::TypeError");
}
return RaiseException.from(context.runtime, cTypeError, message);
}
public static RaiseException createExpectedTypeError(ThreadContext context, String type, String fieldType, String fieldName, IRubyObject value) {
return createTypeError(context, String.format(EXPECTED_TYPE_ERROR_FORMAT, type, fieldType, fieldName, value.getMetaClass()));
}
public static RaiseException createInvalidTypeError(ThreadContext context, String fieldType, String fieldName, IRubyObject value) {
return createTypeError(context, String.format(INVALID_TYPE_ERROR_FORMAT, fieldType, fieldName, value.getMetaClass()));
}
protected static boolean isRubyNum(Object value) {
return value instanceof RubyFixnum || value instanceof RubyFloat || value instanceof RubyBignum;
}
protected static void validateTypeClass(ThreadContext context, FieldDescriptor.Type type, IRubyObject value) {
Ruby runtime = context.runtime;
if (!(value instanceof RubyModule)) {
throw runtime.newArgumentError("TypeClass has incorrect type");
}
RubyModule klass = (RubyModule) value;
IRubyObject descriptor = klass.getInstanceVariable(DESCRIPTOR_INSTANCE_VAR);
if (descriptor.isNil()) {
throw runtime.newArgumentError("Type class has no descriptor. Please pass a " +
"class or enum as returned by the DescriptorPool.");
}
if (type == FieldDescriptor.Type.MESSAGE) {
if (! (descriptor instanceof RubyDescriptor)) {
throw runtime.newArgumentError("Descriptor has an incorrect type");
}
} else if (type == FieldDescriptor.Type.ENUM) {
if (! (descriptor instanceof RubyEnumDescriptor)) {
throw runtime.newArgumentError("Descriptor has an incorrect type");
}
}
}
private static IRubyObject validateAndEncodeString(ThreadContext context, String fieldType, String fieldName, IRubyObject value, String encoding) {
if (!(value instanceof RubyString))
throw createInvalidTypeError(context, fieldType, fieldName, value);
value = ((RubyString) value).encode(context, context.runtime.evalScriptlet(encoding));
value.setFrozen(true);
return value;
}
public static final String DESCRIPTOR_INSTANCE_VAR = "@descriptor";
public static final String EQUAL_SIGN = "=";
private static final BigInteger UINT64_COMPLEMENTARY = new BigInteger("18446744073709551616"); //Math.pow(2, 64)
private static final String EXPECTED_TYPE_ERROR_FORMAT = "Expected %s type for %s field '%s' (given %s).";
private static final String INVALID_TYPE_ERROR_FORMAT = "Invalid argument for %s field '%s' (given %s).";
private static final long UINT_MAX = 0xffffffffl;
private static RubyClass cTypeError;
}

View File

@ -0,0 +1,62 @@
/*
* Protocol Buffers - Google's data interchange format
* Copyright 2014 Google Inc. All rights reserved.
* https://developers.google.com/protocol-buffers/
*
* 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.
*/
package google;
import com.google.protobuf.jruby.*;
import org.jruby.Ruby;
import org.jruby.runtime.load.BasicLibraryService;
import java.io.IOException;
public class ProtobufJavaService implements BasicLibraryService {
@Override
public boolean basicLoad(Ruby ruby) throws IOException {
ruby.defineModule("Google");
/*
* The order these happen in is important because we
* save a static reference to some classes and they
* need to exist before we try to save a reference to them
*/
RubyProtobuf.createProtobuf(ruby);
RubyFileDescriptor.createRubyFileDescriptor(ruby);
RubyEnumDescriptor.createRubyEnumDescriptor(ruby);
RubyRepeatedField.createRubyRepeatedField(ruby);
RubyFieldDescriptor.createRubyFieldDescriptor(ruby);
RubyMap.createRubyMap(ruby);
RubyOneofDescriptor.createRubyOneofDescriptor(ruby);
RubyDescriptor.createRubyDescriptor(ruby);
RubyDescriptorPool.createRubyDescriptorPool(ruby);
return true;
}
}

View File

@ -0,0 +1,15 @@
syntax = "proto3";
package com.google.protobuf.jruby;
option optimize_for = CODE_SIZE;
message Sentinel {
int32 default_int32 = 1;
int64 default_int64 = 2;
uint32 default_unit32 = 3;
uint64 default_uint64 = 4;
string default_string = 5;
bool default_bool = 6;
float default_float = 7;
double default_double = 8;
bytes default_bytes = 9;
}

739
deps/protobuf/ruby/tests/basic.rb vendored Normal file
View File

@ -0,0 +1,739 @@
#!/usr/bin/ruby
# basic_test_pb.rb is in the same directory as this test.
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
require 'basic_test_pb'
require 'common_tests'
require 'google/protobuf'
require 'json'
require 'test/unit'
# ------------- generated code --------------
module BasicTest
pool = Google::Protobuf::DescriptorPool.new
pool.build do
add_message "BadFieldNames" do
optional :dup, :int32, 1
optional :class, :int32, 2
end
end
BadFieldNames = pool.lookup("BadFieldNames").msgclass
# ------------ test cases ---------------
class MessageContainerTest < Test::Unit::TestCase
# Required by CommonTests module to resolve proto3 proto classes used in tests.
def proto_module
::BasicTest
end
include CommonTests
def test_issue_8311_crash
Google::Protobuf::DescriptorPool.generated_pool.build do
add_file("inner.proto", :syntax => :proto3) do
add_message "Inner" do
# Removing either of these fixes the segfault.
optional :foo, :string, 1
optional :bar, :string, 2
end
end
end
Google::Protobuf::DescriptorPool.generated_pool.build do
add_file("outer.proto", :syntax => :proto3) do
add_message "Outer" do
repeated :inners, :message, 1, "Inner"
end
end
end
outer = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("Outer").msgclass
outer.new(
inners: []
)['inners'].to_s
assert_raise Google::Protobuf::TypeError do
outer.new(
inners: [nil]
).to_s
end
end
def test_issue_8559_crash
msg = TestMessage.new
msg.repeated_int32 = ::Google::Protobuf::RepeatedField.new(:int32, [1, 2, 3])
# https://github.com/jruby/jruby/issues/6818 was fixed in JRuby 9.3.0.0
if cruby_or_jruby_9_3_or_higher?
GC.start(full_mark: true, immediate_sweep: true)
end
TestMessage.encode(msg)
end
def test_issue_9440
msg = HelloRequest.new
msg.id = 8
assert_equal 8, msg.id
msg.version = '1'
assert_equal 8, msg.id
end
def test_issue_9507
pool = Google::Protobuf::DescriptorPool.new
pool.build do
add_message "NpeMessage" do
optional :type, :enum, 1, "TestEnum"
optional :other, :string, 2
end
add_enum "TestEnum" do
value :Something, 0
end
end
msgclass = pool.lookup("NpeMessage").msgclass
m = msgclass.new(
other: "foo" # must be set, but can be blank
)
begin
encoded = msgclass.encode(m)
rescue java.lang.NullPointerException
flunk "NPE rescued"
end
decoded = msgclass.decode(encoded)
decoded.inspect
decoded.to_proto
end
def test_has_field
m = TestSingularFields.new
assert !m.has_singular_msg?
m.singular_msg = TestMessage2.new
assert m.has_singular_msg?
assert TestSingularFields.descriptor.lookup('singular_msg').has?(m)
m = OneofMessage.new
assert !m.has_my_oneof?
m.a = "foo"
assert m.has_my_oneof?
assert_raise NoMethodError do
m.has_a?
end
assert_true OneofMessage.descriptor.lookup('a').has?(m)
m = TestSingularFields.new
assert_raise NoMethodError do
m.has_singular_int32?
end
assert_raise ArgumentError do
TestSingularFields.descriptor.lookup('singular_int32').has?(m)
end
assert_raise NoMethodError do
m.has_singular_string?
end
assert_raise ArgumentError do
TestSingularFields.descriptor.lookup('singular_string').has?(m)
end
assert_raise NoMethodError do
m.has_singular_bool?
end
assert_raise ArgumentError do
TestSingularFields.descriptor.lookup('singular_bool').has?(m)
end
m = TestMessage.new
assert_raise NoMethodError do
m.has_repeated_msg?
end
assert_raise ArgumentError do
TestMessage.descriptor.lookup('repeated_msg').has?(m)
end
end
def test_no_presence
m = TestSingularFields.new
# Explicitly setting to zero does not cause anything to be serialized.
m.singular_int32 = 0
assert_equal "", TestSingularFields.encode(m)
# Explicitly setting to a non-zero value *does* cause serialization.
m.singular_int32 = 1
assert_not_equal "", TestSingularFields.encode(m)
m.singular_int32 = 0
assert_equal "", TestSingularFields.encode(m)
end
def test_set_clear_defaults
m = TestSingularFields.new
m.singular_int32 = -42
assert_equal( -42, m.singular_int32 )
m.clear_singular_int32
assert_equal 0, m.singular_int32
m.singular_int32 = 50
assert_equal 50, m.singular_int32
TestSingularFields.descriptor.lookup('singular_int32').clear(m)
assert_equal 0, m.singular_int32
m.singular_string = "foo bar"
assert_equal "foo bar", m.singular_string
m.clear_singular_string
assert_equal "", m.singular_string
m.singular_string = "foo"
assert_equal "foo", m.singular_string
TestSingularFields.descriptor.lookup('singular_string').clear(m)
assert_equal "", m.singular_string
m.singular_msg = TestMessage2.new(:foo => 42)
assert_equal TestMessage2.new(:foo => 42), m.singular_msg
assert m.has_singular_msg?
m.clear_singular_msg
assert_equal nil, m.singular_msg
assert !m.has_singular_msg?
m.singular_msg = TestMessage2.new(:foo => 42)
assert_equal TestMessage2.new(:foo => 42), m.singular_msg
TestSingularFields.descriptor.lookup('singular_msg').clear(m)
assert_equal nil, m.singular_msg
end
def test_import_proto2
m = TestMessage.new
assert !m.has_optional_proto2_submessage?
m.optional_proto2_submessage = ::FooBar::Proto2::TestImportedMessage.new
assert m.has_optional_proto2_submessage?
assert TestMessage.descriptor.lookup('optional_proto2_submessage').has?(m)
m.clear_optional_proto2_submessage
assert !m.has_optional_proto2_submessage?
end
def test_clear_repeated_fields
m = TestMessage.new
m.repeated_int32.push(1)
assert_equal [1], m.repeated_int32
m.clear_repeated_int32
assert_equal [], m.repeated_int32
m.repeated_int32.push(1)
assert_equal [1], m.repeated_int32
TestMessage.descriptor.lookup('repeated_int32').clear(m)
assert_equal [], m.repeated_int32
m = OneofMessage.new
m.a = "foo"
assert_equal "foo", m.a
assert m.has_my_oneof?
assert_equal :a, m.my_oneof
m.clear_a
assert !m.has_my_oneof?
m.a = "foobar"
assert m.has_my_oneof?
m.clear_my_oneof
assert !m.has_my_oneof?
m.a = "bar"
assert_equal "bar", m.a
assert m.has_my_oneof?
OneofMessage.descriptor.lookup('a').clear(m)
assert !m.has_my_oneof?
end
def test_initialization_map_errors
e = assert_raise ArgumentError do
TestMessage.new(:hello => "world")
end
assert_match(/hello/, e.message)
e = assert_raise ArgumentError do
MapMessage.new(:map_string_int32 => "hello")
end
assert_equal e.message, "Expected Hash object as initializer value for map field 'map_string_int32' (given String)."
e = assert_raise ArgumentError do
TestMessage.new(:repeated_uint32 => "hello")
end
assert_equal e.message, "Expected array as initializer value for repeated field 'repeated_uint32' (given String)."
end
def test_map_field
m = MapMessage.new
assert m.map_string_int32 == {}
assert m.map_string_msg == {}
m = MapMessage.new(
:map_string_int32 => {"a" => 1, "b" => 2},
:map_string_msg => {"a" => TestMessage2.new(:foo => 1),
"b" => TestMessage2.new(:foo => 2)},
:map_string_enum => {"a" => :A, "b" => :B})
assert m.map_string_int32.keys.sort == ["a", "b"]
assert m.map_string_int32["a"] == 1
assert m.map_string_msg["b"].foo == 2
assert m.map_string_enum["a"] == :A
m.map_string_int32["c"] = 3
assert m.map_string_int32["c"] == 3
m.map_string_msg["c"] = TestMessage2.new(:foo => 3)
assert m.map_string_msg["c"] == TestMessage2.new(:foo => 3)
m.map_string_msg.delete("b")
m.map_string_msg.delete("c")
assert m.map_string_msg == { "a" => TestMessage2.new(:foo => 1) }
assert_raise Google::Protobuf::TypeError do
m.map_string_msg["e"] = TestMessage.new # wrong value type
end
# ensure nothing was added by the above
assert m.map_string_msg == { "a" => TestMessage2.new(:foo => 1) }
m.map_string_int32 = Google::Protobuf::Map.new(:string, :int32)
assert_raise Google::Protobuf::TypeError do
m.map_string_int32 = Google::Protobuf::Map.new(:string, :int64)
end
assert_raise Google::Protobuf::TypeError do
m.map_string_int32 = {}
end
assert_raise Google::Protobuf::TypeError do
m = MapMessage.new(:map_string_int32 => { 1 => "I am not a number" })
end
end
def test_map_field_with_symbol
m = MapMessage.new
assert m.map_string_int32 == {}
assert m.map_string_msg == {}
m = MapMessage.new(
:map_string_int32 => {a: 1, "b" => 2},
:map_string_msg => {a: TestMessage2.new(:foo => 1),
b: TestMessage2.new(:foo => 10)})
assert_equal 1, m.map_string_int32[:a]
assert_equal 2, m.map_string_int32[:b]
assert_equal 10, m.map_string_msg[:b].foo
end
def test_map_inspect
m = MapMessage.new(
:map_string_int32 => {"a" => 1, "b" => 2},
:map_string_msg => {"a" => TestMessage2.new(:foo => 1),
"b" => TestMessage2.new(:foo => 2)},
:map_string_enum => {"a" => :A, "b" => :B})
# JRuby doesn't keep consistent ordering so check for either version
expected_a = "<BasicTest::MapMessage: map_string_int32: {\"b\"=>2, \"a\"=>1}, map_string_msg: {\"b\"=><BasicTest::TestMessage2: foo: 2>, \"a\"=><BasicTest::TestMessage2: foo: 1>}, map_string_enum: {\"b\"=>:B, \"a\"=>:A}>"
expected_b = "<BasicTest::MapMessage: map_string_int32: {\"a\"=>1, \"b\"=>2}, map_string_msg: {\"a\"=><BasicTest::TestMessage2: foo: 1>, \"b\"=><BasicTest::TestMessage2: foo: 2>}, map_string_enum: {\"a\"=>:A, \"b\"=>:B}>"
inspect_result = m.inspect
assert expected_a == inspect_result || expected_b == inspect_result, "Incorrect inspect result: #{inspect_result}"
end
def test_map_corruption
# This pattern led to a crash in a previous version of upb/protobuf.
m = MapMessage.new(map_string_int32: { "aaa" => 1 })
m.map_string_int32['podid'] = 2
m.map_string_int32['aaa'] = 3
end
def test_map_wrappers
run_asserts = ->(m) {
assert_equal 2.0, m.map_double[0].value
assert_equal 4.0, m.map_float[0].value
assert_equal 3, m.map_int32[0].value
assert_equal 4, m.map_int64[0].value
assert_equal 5, m.map_uint32[0].value
assert_equal 6, m.map_uint64[0].value
assert_equal true, m.map_bool[0].value
assert_equal 'str', m.map_string[0].value
assert_equal 'fun', m.map_bytes[0].value
}
m = proto_module::Wrapper.new(
map_double: {0 => Google::Protobuf::DoubleValue.new(value: 2.0)},
map_float: {0 => Google::Protobuf::FloatValue.new(value: 4.0)},
map_int32: {0 => Google::Protobuf::Int32Value.new(value: 3)},
map_int64: {0 => Google::Protobuf::Int64Value.new(value: 4)},
map_uint32: {0 => Google::Protobuf::UInt32Value.new(value: 5)},
map_uint64: {0 => Google::Protobuf::UInt64Value.new(value: 6)},
map_bool: {0 => Google::Protobuf::BoolValue.new(value: true)},
map_string: {0 => Google::Protobuf::StringValue.new(value: 'str')},
map_bytes: {0 => Google::Protobuf::BytesValue.new(value: 'fun')},
)
run_asserts.call(m)
serialized = proto_module::Wrapper::encode(m)
m2 = proto_module::Wrapper::decode(serialized)
run_asserts.call(m2)
# Test the case where we are serializing directly from the parsed form
# (before anything lazy is materialized).
m3 = proto_module::Wrapper::decode(serialized)
serialized2 = proto_module::Wrapper::encode(m3)
m4 = proto_module::Wrapper::decode(serialized2)
run_asserts.call(m4)
# Test that the lazy form compares equal to the expanded form.
m5 = proto_module::Wrapper::decode(serialized2)
assert_equal m5, m
end
def test_map_wrappers_with_default_values
run_asserts = ->(m) {
assert_equal 0.0, m.map_double[0].value
assert_equal 0.0, m.map_float[0].value
assert_equal 0, m.map_int32[0].value
assert_equal 0, m.map_int64[0].value
assert_equal 0, m.map_uint32[0].value
assert_equal 0, m.map_uint64[0].value
assert_equal false, m.map_bool[0].value
assert_equal '', m.map_string[0].value
assert_equal '', m.map_bytes[0].value
}
m = proto_module::Wrapper.new(
map_double: {0 => Google::Protobuf::DoubleValue.new(value: 0.0)},
map_float: {0 => Google::Protobuf::FloatValue.new(value: 0.0)},
map_int32: {0 => Google::Protobuf::Int32Value.new(value: 0)},
map_int64: {0 => Google::Protobuf::Int64Value.new(value: 0)},
map_uint32: {0 => Google::Protobuf::UInt32Value.new(value: 0)},
map_uint64: {0 => Google::Protobuf::UInt64Value.new(value: 0)},
map_bool: {0 => Google::Protobuf::BoolValue.new(value: false)},
map_string: {0 => Google::Protobuf::StringValue.new(value: '')},
map_bytes: {0 => Google::Protobuf::BytesValue.new(value: '')},
)
run_asserts.call(m)
serialized = proto_module::Wrapper::encode(m)
m2 = proto_module::Wrapper::decode(serialized)
run_asserts.call(m2)
# Test the case where we are serializing directly from the parsed form
# (before anything lazy is materialized).
m3 = proto_module::Wrapper::decode(serialized)
serialized2 = proto_module::Wrapper::encode(m3)
m4 = proto_module::Wrapper::decode(serialized2)
run_asserts.call(m4)
# Test that the lazy form compares equal to the expanded form.
m5 = proto_module::Wrapper::decode(serialized2)
assert_equal m5, m
end
def test_map_wrappers_with_no_value
run_asserts = ->(m) {
assert_equal 0.0, m.map_double[0].value
assert_equal 0.0, m.map_float[0].value
assert_equal 0, m.map_int32[0].value
assert_equal 0, m.map_int64[0].value
assert_equal 0, m.map_uint32[0].value
assert_equal 0, m.map_uint64[0].value
assert_equal false, m.map_bool[0].value
assert_equal '', m.map_string[0].value
assert_equal '', m.map_bytes[0].value
}
m = proto_module::Wrapper.new(
map_double: {0 => Google::Protobuf::DoubleValue.new()},
map_float: {0 => Google::Protobuf::FloatValue.new()},
map_int32: {0 => Google::Protobuf::Int32Value.new()},
map_int64: {0 => Google::Protobuf::Int64Value.new()},
map_uint32: {0 => Google::Protobuf::UInt32Value.new()},
map_uint64: {0 => Google::Protobuf::UInt64Value.new()},
map_bool: {0 => Google::Protobuf::BoolValue.new()},
map_string: {0 => Google::Protobuf::StringValue.new()},
map_bytes: {0 => Google::Protobuf::BytesValue.new()},
)
run_asserts.call(m)
serialized = proto_module::Wrapper::encode(m)
m2 = proto_module::Wrapper::decode(serialized)
run_asserts.call(m2)
# Test the case where we are serializing directly from the parsed form
# (before anything lazy is materialized).
m3 = proto_module::Wrapper::decode(serialized)
serialized2 = proto_module::Wrapper::encode(m3)
m4 = proto_module::Wrapper::decode(serialized2)
run_asserts.call(m4)
end
def test_concurrent_decoding
o = Outer.new
o.items[0] = Inner.new
raw = Outer.encode(o)
thds = 2.times.map do
Thread.new do
100000.times do
assert_equal o, Outer.decode(raw)
end
end
end
thds.map(&:join)
end
def test_map_encode_decode
m = MapMessage.new(
:map_string_int32 => {"a" => 1, "b" => 2},
:map_string_msg => {"a" => TestMessage2.new(:foo => 1),
"b" => TestMessage2.new(:foo => 2)},
:map_string_enum => {"a" => :A, "b" => :B})
m2 = MapMessage.decode(MapMessage.encode(m))
assert m == m2
m3 = MapMessageWireEquiv.decode(MapMessage.encode(m))
assert m3.map_string_int32.length == 2
kv = {}
m3.map_string_int32.map { |msg| kv[msg.key] = msg.value }
assert kv == {"a" => 1, "b" => 2}
kv = {}
m3.map_string_msg.map { |msg| kv[msg.key] = msg.value }
assert kv == {"a" => TestMessage2.new(:foo => 1),
"b" => TestMessage2.new(:foo => 2)}
end
def test_protobuf_decode_json_ignore_unknown_fields
m = TestMessage.decode_json({
optional_string: "foo",
not_in_message: "some_value"
}.to_json, { ignore_unknown_fields: true })
assert_equal m.optional_string, "foo"
e = assert_raise Google::Protobuf::ParseError do
TestMessage.decode_json({ not_in_message: "some_value" }.to_json)
end
assert_match(/No such field: not_in_message/, e.message)
end
#def test_json_quoted_string
# m = TestMessage.decode_json(%q(
# "optionalInt64": "1",,
# }))
# puts(m)
# assert_equal 1, m.optional_int32
#end
def test_to_h
m = TestMessage.new(:optional_bool => true, :optional_double => -10.100001, :optional_string => 'foo', :repeated_string => ['bar1', 'bar2'], :repeated_msg => [TestMessage2.new(:foo => 100)])
expected_result = {
:optional_bool=>true,
:optional_bytes=>"",
:optional_double=>-10.100001,
:optional_enum=>:Default,
:optional_float=>0.0,
:optional_int32=>0,
:optional_int64=>0,
:optional_msg=>nil,
:optional_msg2=>nil,
:optional_proto2_submessage=>nil,
:optional_string=>"foo",
:optional_uint32=>0,
:optional_uint64=>0,
:repeated_bool=>[],
:repeated_bytes=>[],
:repeated_double=>[],
:repeated_enum=>[],
:repeated_float=>[],
:repeated_int32=>[],
:repeated_int64=>[],
:repeated_msg=>[{:foo => 100}],
:repeated_string=>["bar1", "bar2"],
:repeated_uint32=>[],
:repeated_uint64=>[]
}
assert_equal expected_result, m.to_h
m = MapMessage.new(
:map_string_int32 => {"a" => 1, "b" => 2},
:map_string_msg => {"a" => TestMessage2.new(:foo => 1),
"b" => TestMessage2.new(:foo => 2)},
:map_string_enum => {"a" => :A, "b" => :B})
expected_result = {
:map_string_int32 => {"a" => 1, "b" => 2},
:map_string_msg => {"a" => {:foo => 1}, "b" => {:foo => 2}},
:map_string_enum => {"a" => :A, "b" => :B}
}
assert_equal expected_result, m.to_h
end
def test_json_maps
m = MapMessage.new(:map_string_int32 => {"a" => 1})
expected = {mapStringInt32: {a: 1}, mapStringMsg: {}, mapStringEnum: {}}
expected_preserve = {map_string_int32: {a: 1}, map_string_msg: {}, map_string_enum: {}}
assert_equal JSON.parse(MapMessage.encode_json(m, :emit_defaults=>true), :symbolize_names => true), expected
json = MapMessage.encode_json(m, :preserve_proto_fieldnames => true, :emit_defaults=>true)
assert_equal JSON.parse(json, :symbolize_names => true), expected_preserve
m2 = MapMessage.decode_json(MapMessage.encode_json(m))
assert_equal m, m2
end
def test_json_maps_emit_defaults_submsg
m = MapMessage.new(:map_string_msg => {"a" => TestMessage2.new(foo: 0)})
expected = {mapStringInt32: {}, mapStringMsg: {a: {foo: 0}}, mapStringEnum: {}}
actual = MapMessage.encode_json(m, :emit_defaults => true)
assert_equal JSON.parse(actual, :symbolize_names => true), expected
end
def test_json_emit_defaults_submsg
m = TestSingularFields.new(singular_msg: proto_module::TestMessage2.new)
expected = {
singularInt32: 0,
singularInt64: "0",
singularUint32: 0,
singularUint64: "0",
singularBool: false,
singularFloat: 0,
singularDouble: 0,
singularString: "",
singularBytes: "",
singularMsg: {},
singularEnum: "Default",
}
actual = proto_module::TestMessage.encode_json(m, :emit_defaults => true)
assert_equal expected, JSON.parse(actual, :symbolize_names => true)
end
def test_respond_to
msg = MapMessage.new
assert msg.respond_to?(:map_string_int32)
assert !msg.respond_to?(:bacon)
end
def test_file_descriptor
file_descriptor = TestMessage.descriptor.file_descriptor
assert nil != file_descriptor
assert_equal "tests/basic_test.proto", file_descriptor.name
assert_equal :proto3, file_descriptor.syntax
file_descriptor = TestEnum.descriptor.file_descriptor
assert nil != file_descriptor
assert_equal "tests/basic_test.proto", file_descriptor.name
assert_equal :proto3, file_descriptor.syntax
end
# Ruby 2.5 changed to raise FrozenError instead of RuntimeError
FrozenErrorType = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.5') ? RuntimeError : FrozenError
def test_map_freeze
m = proto_module::MapMessage.new
m.map_string_int32['a'] = 5
m.map_string_msg['b'] = proto_module::TestMessage2.new
m.map_string_int32.freeze
m.map_string_msg.freeze
assert m.map_string_int32.frozen?
assert m.map_string_msg.frozen?
assert_raise(FrozenErrorType) { m.map_string_int32['foo'] = 1 }
assert_raise(FrozenErrorType) { m.map_string_msg['bar'] = proto_module::TestMessage2.new }
assert_raise(FrozenErrorType) { m.map_string_int32.delete('a') }
assert_raise(FrozenErrorType) { m.map_string_int32.clear }
end
def test_map_length
m = proto_module::MapMessage.new
assert_equal 0, m.map_string_int32.length
assert_equal 0, m.map_string_msg.length
assert_equal 0, m.map_string_int32.size
assert_equal 0, m.map_string_msg.size
m.map_string_int32['a'] = 1
m.map_string_int32['b'] = 2
m.map_string_msg['a'] = proto_module::TestMessage2.new
assert_equal 2, m.map_string_int32.length
assert_equal 1, m.map_string_msg.length
assert_equal 2, m.map_string_int32.size
assert_equal 1, m.map_string_msg.size
end
def test_string_with_singleton_class_enabled
str = 'foobar'
# NOTE: Accessing a singleton class of an object changes its low level class representation
# as far as the C API's CLASS_OF() method concerned, exposing the issue
str.singleton_class
m = proto_module::TestMessage.new(
optional_string: str,
optional_bytes: str
)
assert_equal str, m.optional_string
assert_equal str, m.optional_bytes
end
def test_utf8
m = proto_module::TestMessage.new(
optional_string: "µpb",
)
m2 = proto_module::TestMessage.decode(proto_module::TestMessage.encode(m))
assert_equal m2, m
end
def test_map_fields_respond_to? # regression test for issue 9202
msg = proto_module::MapMessage.new
assert msg.respond_to?(:map_string_int32=)
msg.map_string_int32 = Google::Protobuf::Map.new(:string, :int32)
assert msg.respond_to?(:map_string_int32)
assert_equal( Google::Protobuf::Map.new(:string, :int32), msg.map_string_int32 )
assert msg.respond_to?(:clear_map_string_int32)
msg.clear_map_string_int32
assert !msg.respond_to?(:has_map_string_int32?)
assert_raise NoMethodError do
msg.has_map_string_int32?
end
assert !msg.respond_to?(:map_string_int32_as_value)
assert_raise NoMethodError do
msg.map_string_int32_as_value
end
assert !msg.respond_to?(:map_string_int32_as_value=)
assert_raise NoMethodError do
msg.map_string_int32_as_value = :boom
end
end
end
def test_oneof_fields_respond_to? # regression test for issue 9202
msg = proto_module::OneofMessage.new
# `has_` prefix + "?" suffix actions should only work for oneofs fields.
assert msg.has_my_oneof?
assert msg.respond_to? :has_my_oneof?
assert !msg.respond_to?( :has_a? )
assert_raise NoMethodError do
msg.has_a?
end
assert !msg.respond_to?( :has_b? )
assert_raise NoMethodError do
msg.has_b?
end
assert !msg.respond_to?( :has_c? )
assert_raise NoMethodError do
msg.has_c?
end
assert !msg.respond_to?( :has_d? )
assert_raise NoMethodError do
msg.has_d?
end
end
end

274
deps/protobuf/ruby/tests/basic_proto2.rb vendored Normal file
View File

@ -0,0 +1,274 @@
#!/usr/bin/ruby
# basic_test_pb.rb is in the same directory as this test.
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
require 'basic_test_proto2_pb'
require 'common_tests'
require 'google/protobuf'
require 'json'
require 'test/unit'
# ------------- generated code --------------
module BasicTestProto2
pool = Google::Protobuf::DescriptorPool.new
pool.build do
add_file "test_proto2.proto", syntax: :proto2 do
add_message "BadFieldNames" do
optional :dup, :int32, 1
optional :class, :int32, 2
end
end
end
BadFieldNames = pool.lookup("BadFieldNames").msgclass
# ------------ test cases ---------------
class MessageContainerTest < Test::Unit::TestCase
# Required by CommonTests module to resolve proto2 proto classes used in tests.
def proto_module
::BasicTestProto2
end
include CommonTests
def test_has_field
m = TestMessage.new
assert !m.has_optional_int32?
assert !TestMessage.descriptor.lookup('optional_int32').has?(m)
assert !m.has_optional_int64?
assert !TestMessage.descriptor.lookup('optional_int64').has?(m)
assert !m.has_optional_uint32?
assert !TestMessage.descriptor.lookup('optional_uint32').has?(m)
assert !m.has_optional_uint64?
assert !TestMessage.descriptor.lookup('optional_uint64').has?(m)
assert !m.has_optional_bool?
assert !TestMessage.descriptor.lookup('optional_bool').has?(m)
assert !m.has_optional_float?
assert !TestMessage.descriptor.lookup('optional_float').has?(m)
assert !m.has_optional_double?
assert !TestMessage.descriptor.lookup('optional_double').has?(m)
assert !m.has_optional_string?
assert !TestMessage.descriptor.lookup('optional_string').has?(m)
assert !m.has_optional_bytes?
assert !TestMessage.descriptor.lookup('optional_bytes').has?(m)
assert !m.has_optional_enum?
assert !TestMessage.descriptor.lookup('optional_enum').has?(m)
m = TestMessage.new(:optional_int32 => nil)
assert !m.has_optional_int32?
assert_raise NoMethodError do
m.has_repeated_msg?
end
assert_raise ArgumentError do
TestMessage.descriptor.lookup('repeated_msg').has?(m)
end
m.optional_msg = TestMessage2.new
assert m.has_optional_msg?
assert TestMessage.descriptor.lookup('optional_msg').has?(m)
m = OneofMessage.new
assert !m.has_my_oneof?
m.a = "foo"
assert m.has_my_oneof?
assert_equal :a, m.my_oneof
assert m.has_a?
assert OneofMessage.descriptor.lookup('a').has?(m)
assert_equal "foo", m.a
assert !m.has_b?
assert !OneofMessage.descriptor.lookup('b').has?(m)
assert !m.has_c?
assert !OneofMessage.descriptor.lookup('c').has?(m)
assert !m.has_d?
assert !OneofMessage.descriptor.lookup('d').has?(m)
m = OneofMessage.new
m.b = 100
assert m.has_b?
assert_equal 100, m.b
assert m.has_my_oneof?
assert !m.has_a?
assert !m.has_c?
assert !m.has_d?
m = OneofMessage.new
m.c = TestMessage2.new
assert m.has_c?
assert_equal TestMessage2.new, m.c
assert m.has_my_oneof?
assert !m.has_a?
assert !m.has_b?
assert !m.has_d?
m = OneofMessage.new
m.d = :A
assert m.has_d?
assert_equal :A, m.d
assert m.has_my_oneof?
assert !m.has_a?
assert !m.has_b?
assert !m.has_c?
end
def test_defined_defaults
m = TestMessageDefaults.new
assert_equal 1, m.optional_int32
assert_equal 2, m.optional_int64
assert_equal 3, m.optional_uint32
assert_equal 4, m.optional_uint64
assert_equal true, m.optional_bool
assert_equal 6.0, m.optional_float
assert_equal 7.0, m.optional_double
assert_equal "Default Str", m.optional_string
assert_equal "\xCF\xA5s\xBD\xBA\xE6fubar".force_encoding("ASCII-8BIT"), m.optional_bytes
assert_equal :B2, m.optional_enum
assert !m.has_optional_int32?
assert !m.has_optional_int64?
assert !m.has_optional_uint32?
assert !m.has_optional_uint64?
assert !m.has_optional_bool?
assert !m.has_optional_float?
assert !m.has_optional_double?
assert !m.has_optional_string?
assert !m.has_optional_bytes?
assert !m.has_optional_enum?
end
def test_set_clear_defaults
m = TestMessageDefaults.new
m.optional_int32 = -42
assert_equal( -42, m.optional_int32 )
assert m.has_optional_int32?
m.clear_optional_int32
assert_equal 1, m.optional_int32
assert !m.has_optional_int32?
m.optional_string = "foo bar"
assert_equal "foo bar", m.optional_string
assert m.has_optional_string?
m.clear_optional_string
assert_equal "Default Str", m.optional_string
assert !m.has_optional_string?
m.optional_msg = TestMessage2.new(:foo => 42)
assert_equal TestMessage2.new(:foo => 42), m.optional_msg
assert m.has_optional_msg?
m.clear_optional_msg
assert_equal nil, m.optional_msg
assert !m.has_optional_msg?
m.optional_msg = TestMessage2.new(:foo => 42)
assert_equal TestMessage2.new(:foo => 42), m.optional_msg
assert TestMessageDefaults.descriptor.lookup('optional_msg').has?(m)
TestMessageDefaults.descriptor.lookup('optional_msg').clear(m)
assert_equal nil, m.optional_msg
assert !TestMessageDefaults.descriptor.lookup('optional_msg').has?(m)
m = TestMessage.new
m.repeated_int32.push(1)
assert_equal [1], m.repeated_int32
m.clear_repeated_int32
assert_equal [], m.repeated_int32
m = OneofMessage.new
m.a = "foo"
assert_equal "foo", m.a
assert m.has_a?
m.clear_a
assert !m.has_a?
m = OneofMessage.new
m.a = "foobar"
assert m.has_my_oneof?
m.clear_my_oneof
assert !m.has_my_oneof?
m = OneofMessage.new
m.a = "bar"
assert_equal "bar", m.a
assert m.has_my_oneof?
OneofMessage.descriptor.lookup('a').clear(m)
assert !m.has_my_oneof?
end
def test_assign_nil
m = TestMessageDefaults.new
m.optional_msg = TestMessage2.new(:foo => 42)
assert_equal TestMessage2.new(:foo => 42), m.optional_msg
assert m.has_optional_msg?
m.optional_msg = nil
assert_equal nil, m.optional_msg
assert !m.has_optional_msg?
end
def test_initialization_map_errors
e = assert_raise ArgumentError do
TestMessage.new(:hello => "world")
end
assert_match(/hello/, e.message)
e = assert_raise ArgumentError do
TestMessage.new(:repeated_uint32 => "hello")
end
assert_equal e.message, "Expected array as initializer value for repeated field 'repeated_uint32' (given String)."
end
def test_to_h
m = TestMessage.new(:optional_bool => true, :optional_double => -10.100001, :optional_string => 'foo', :repeated_string => ['bar1', 'bar2'])
expected_result = {
:optional_bool=>true,
:optional_double=>-10.100001,
:optional_string=>"foo",
:repeated_string=>["bar1", "bar2"],
}
assert_equal expected_result, m.to_h
m = OneofMessage.new(:a => "foo")
expected_result = {:a => "foo"}
assert_equal expected_result, m.to_h
end
def test_respond_to
# This test fails with JRuby 1.7.23, likely because of an old JRuby bug.
return if RUBY_PLATFORM == "java"
msg = TestMessage.new
assert !msg.respond_to?(:bacon)
end
def test_file_descriptor
file_descriptor = TestMessage.descriptor.file_descriptor
assert nil != file_descriptor
assert_equal "tests/basic_test_proto2.proto", file_descriptor.name
assert_equal :proto2, file_descriptor.syntax
file_descriptor = TestEnum.descriptor.file_descriptor
assert nil != file_descriptor
assert_equal "tests/basic_test_proto2.proto", file_descriptor.name
assert_equal :proto2, file_descriptor.syntax
end
def test_oneof_fields_respond_to? # regression test for issue 9202
msg = proto_module::OneofMessage.new(a: "foo")
# `has_` prefix + "?" suffix actions should only work for oneofs fields.
assert msg.respond_to? :has_my_oneof?
assert msg.has_my_oneof?
assert msg.respond_to? :has_a?
assert msg.has_a?
assert msg.respond_to? :has_b?
assert !msg.has_b?
assert msg.respond_to? :has_c?
assert !msg.has_c?
assert msg.respond_to? :has_d?
assert !msg.has_d?
end
end
end

View File

@ -0,0 +1,252 @@
syntax = "proto3";
package basic_test;
import "google/protobuf/wrappers.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/struct.proto";
import "test_import_proto2.proto";
message Foo {
Bar bar = 1;
repeated Baz baz = 2;
}
message Bar {
string msg = 1;
}
message Baz {
string msg = 1;
}
message TestMessage {
optional int32 optional_int32 = 1;
optional int64 optional_int64 = 2;
optional uint32 optional_uint32 = 3;
optional uint64 optional_uint64 = 4;
optional bool optional_bool = 5;
optional float optional_float = 6;
optional double optional_double = 7;
optional string optional_string = 8;
optional bytes optional_bytes = 9;
optional TestMessage2 optional_msg = 10;
optional TestEnum optional_enum = 11;
optional foo_bar.proto2.TestImportedMessage optional_proto2_submessage = 24;
repeated int32 repeated_int32 = 12;
repeated int64 repeated_int64 = 13;
repeated uint32 repeated_uint32 = 14;
repeated uint64 repeated_uint64 = 15;
repeated bool repeated_bool = 16;
repeated float repeated_float = 17;
repeated double repeated_double = 18;
repeated string repeated_string = 19;
repeated bytes repeated_bytes = 20;
repeated TestMessage2 repeated_msg = 21;
repeated TestEnum repeated_enum = 22;
optional TestSingularFields optional_msg2 = 23;
}
message TestSingularFields {
int32 singular_int32 = 1;
int64 singular_int64 = 2;
uint32 singular_uint32 = 3;
uint64 singular_uint64 = 4;
bool singular_bool = 5;
float singular_float = 6;
double singular_double = 7;
string singular_string = 8;
bytes singular_bytes = 9;
TestMessage2 singular_msg = 10;
TestEnum singular_enum = 11;
}
message TestMessage2 {
optional int32 foo = 1;
}
enum TestEnum {
Default = 0;
A = 1;
B = 2;
C = 3;
}
message TestEmbeddedMessageParent {
TestEmbeddedMessageChild child_msg = 1;
int32 number = 2;
repeated TestEmbeddedMessageChild repeated_msg = 3;
repeated int32 repeated_number = 4;
}
message TestEmbeddedMessageChild {
TestMessage sub_child = 1;
}
message Recursive1 {
Recursive2 foo = 1;
}
message Recursive2 {
Recursive1 foo = 1;
}
message MapMessage {
map<string, int32> map_string_int32 = 1;
map<string, TestMessage2> map_string_msg = 2;
map<string, TestEnum> map_string_enum = 3;
}
message MapMessageWireEquiv {
repeated MapMessageWireEquiv_entry1 map_string_int32 = 1;
repeated MapMessageWireEquiv_entry2 map_string_msg = 2;
}
message MapMessageWireEquiv_entry1 {
string key = 1;
int32 value = 2;
}
message MapMessageWireEquiv_entry2 {
string key = 1;
TestMessage2 value = 2;
}
message OneofMessage {
oneof my_oneof {
string a = 1;
int32 b = 2;
TestMessage2 c = 3;
TestEnum d = 4;
}
}
message Outer {
map<int32, Inner> items = 1;
}
message Inner {
}
message Wrapper {
google.protobuf.DoubleValue double = 1;
google.protobuf.FloatValue float = 2;
google.protobuf.Int32Value int32 = 3;
google.protobuf.Int64Value int64 = 4;
google.protobuf.UInt32Value uint32 = 5;
google.protobuf.UInt64Value uint64 = 6;
google.protobuf.BoolValue bool = 7;
google.protobuf.StringValue string = 8;
google.protobuf.BytesValue bytes = 9;
string real_string = 100;
oneof a_oneof {
string string_in_oneof = 10;
}
// Repeated wrappers don't make sense, but we still need to make sure they
// work and don't crash.
repeated google.protobuf.DoubleValue repeated_double = 11;
repeated google.protobuf.FloatValue repeated_float = 12;
repeated google.protobuf.Int32Value repeated_int32 = 13;
repeated google.protobuf.Int64Value repeated_int64 = 14;
repeated google.protobuf.UInt32Value repeated_uint32 = 15;
repeated google.protobuf.UInt64Value repeated_uint64 = 16;
repeated google.protobuf.BoolValue repeated_bool = 17;
repeated google.protobuf.StringValue repeated_string = 18;
repeated google.protobuf.BytesValue repeated_bytes = 19;
// Wrappers as map keys don't make sense, but we still need to make sure they
// work and don't crash.
map<int32, google.protobuf.DoubleValue> map_double = 21;
map<int32, google.protobuf.FloatValue> map_float = 22;
map<int32, google.protobuf.Int32Value> map_int32 = 23;
map<int32, google.protobuf.Int64Value> map_int64 = 24;
map<int32, google.protobuf.UInt32Value> map_uint32 = 25;
map<int32, google.protobuf.UInt64Value> map_uint64 = 26;
map<int32, google.protobuf.BoolValue> map_bool = 27;
map<int32, google.protobuf.StringValue> map_string = 28;
map<int32, google.protobuf.BytesValue> map_bytes = 29;
// Wrappers in oneofs don't make sense, but we still need to make sure they
// work and don't crash.
oneof wrapper_oneof {
google.protobuf.DoubleValue oneof_double = 31;
google.protobuf.FloatValue oneof_float = 32;
google.protobuf.Int32Value oneof_int32 = 33;
google.protobuf.Int64Value oneof_int64 = 34;
google.protobuf.UInt32Value oneof_uint32 = 35;
google.protobuf.UInt64Value oneof_uint64 = 36;
google.protobuf.BoolValue oneof_bool = 37;
google.protobuf.StringValue oneof_string = 38;
google.protobuf.BytesValue oneof_bytes = 39;
string oneof_plain_string = 101;
}
}
message TimeMessage {
google.protobuf.Timestamp timestamp = 1;
google.protobuf.Duration duration = 2;
}
message Enumer {
TestEnum optional_enum = 1;
repeated TestEnum repeated_enum = 2;
string a_const = 3;
oneof a_oneof {
string str = 10;
TestEnum const = 11;
}
}
message MyRepeatedStruct {
repeated MyStruct structs = 1;
}
message MyStruct {
string string = 1;
google.protobuf.Struct struct = 2;
}
message WithJsonName {
optional int32 foo_bar = 1 [json_name="jsonFooBar"];
repeated WithJsonName baz = 2 [json_name="jsonBaz"];
}
message HelloRequest {
optional uint32 id = 1;
optional uint32 random_name_a0 = 2;
optional uint32 random_name_a1 = 3;
optional uint32 random_name_a2 = 4;
optional uint32 random_name_a3 = 5;
optional uint32 random_name_a4 = 6;
optional uint32 random_name_a5 = 7;
optional uint32 random_name_a6 = 8;
optional uint32 random_name_a7 = 9;
optional uint32 random_name_a8 = 10;
optional uint32 random_name_a9 = 11;
optional uint32 random_name_b0 = 12;
optional uint32 random_name_b1 = 13;
optional uint32 random_name_b2 = 14;
optional uint32 random_name_b3 = 15;
optional uint32 random_name_b4 = 16;
optional uint32 random_name_b5 = 17;
optional uint32 random_name_b6 = 18;
optional uint32 random_name_b7 = 19;
optional uint32 random_name_b8 = 20;
optional uint32 random_name_b9 = 21;
optional uint32 random_name_c0 = 22;
optional uint32 random_name_c1 = 23;
optional uint32 random_name_c2 = 24;
optional uint32 random_name_c3 = 25;
optional uint32 random_name_c4 = 26;
optional uint32 random_name_c5 = 27;
optional uint32 random_name_c6 = 28;
optional uint32 random_name_c7 = 29;
optional uint32 random_name_c8 = 30;
optional uint32 random_name_c9 = 31;
optional string version = 32;
}

View File

@ -0,0 +1,189 @@
syntax = "proto2";
package basic_test_proto2;
import "google/protobuf/wrappers.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/struct.proto";
message Foo {
optional Bar bar = 1;
repeated Baz baz = 2;
}
message Bar {
optional string msg = 1;
}
message Baz {
optional string msg = 1;
}
message TestMessage {
optional int32 optional_int32 = 1;
optional int64 optional_int64 = 2;
optional uint32 optional_uint32 = 3;
optional uint64 optional_uint64 = 4;
optional bool optional_bool = 5;
optional float optional_float = 6;
optional double optional_double = 7;
optional string optional_string = 8;
optional bytes optional_bytes = 9;
optional TestMessage2 optional_msg = 10;
optional TestEnum optional_enum = 11;
repeated int32 repeated_int32 = 12;
repeated int64 repeated_int64 = 13;
repeated uint32 repeated_uint32 = 14;
repeated uint64 repeated_uint64 = 15;
repeated bool repeated_bool = 16;
repeated float repeated_float = 17;
repeated double repeated_double = 18;
repeated string repeated_string = 19;
repeated bytes repeated_bytes = 20;
repeated TestMessage2 repeated_msg = 21;
repeated TestEnum repeated_enum = 22;
}
message TestMessage2 {
optional int32 foo = 1;
}
message TestMessageDefaults {
optional int32 optional_int32 = 1 [default = 1];
optional int64 optional_int64 = 2 [default = 2];
optional uint32 optional_uint32 = 3 [default = 3];
optional uint64 optional_uint64 = 4 [default = 4];
optional bool optional_bool = 5 [default = true];
optional float optional_float = 6 [default = 6];
optional double optional_double = 7 [default = 7];
optional string optional_string = 8 [default = "Default Str"];
optional bytes optional_bytes = 9 [default = "\xCF\xA5s\xBD\xBA\xE6fubar"];
optional TestMessage2 optional_msg = 10;
optional TestNonZeroEnum optional_enum = 11 [default = B2];
}
enum TestEnum {
Default = 0;
A = 1;
B = 2;
C = 3;
}
enum TestNonZeroEnum {
A2 = 1;
B2 = 2;
C2 = 3;
}
message TestEmbeddedMessageParent {
optional TestEmbeddedMessageChild child_msg = 1;
optional int32 number = 2;
repeated TestEmbeddedMessageChild repeated_msg = 3;
repeated int32 repeated_number = 4;
}
message TestEmbeddedMessageChild {
optional TestMessage sub_child = 1;
}
message Recursive1 {
optional Recursive2 foo = 1;
}
message Recursive2 {
optional Recursive1 foo = 1;
}
message MapMessageWireEquiv {
repeated MapMessageWireEquiv_entry1 map_string_int32 = 1;
repeated MapMessageWireEquiv_entry2 map_string_msg = 2;
}
message MapMessageWireEquiv_entry1 {
optional string key = 1;
optional int32 value = 2;
}
message MapMessageWireEquiv_entry2 {
optional string key = 1;
optional TestMessage2 value = 2;
}
message OneofMessage {
oneof my_oneof {
string a = 1;
int32 b = 2;
TestMessage2 c = 3;
TestEnum d = 4;
}
}
message Wrapper {
optional google.protobuf.DoubleValue double = 1;
optional google.protobuf.FloatValue float = 2;
optional google.protobuf.Int32Value int32 = 3;
optional google.protobuf.Int64Value int64 = 4;
optional google.protobuf.UInt32Value uint32 = 5;
optional google.protobuf.UInt64Value uint64 = 6;
optional google.protobuf.BoolValue bool = 7;
optional google.protobuf.StringValue string = 8;
optional google.protobuf.BytesValue bytes = 9;
optional string real_string = 100;
oneof a_oneof {
string string_in_oneof = 10;
}
// Repeated wrappers don't really make sense, but we still need to make sure
// they work and don't crash.
repeated google.protobuf.DoubleValue repeated_double = 11;
repeated google.protobuf.FloatValue repeated_float = 12;
repeated google.protobuf.Int32Value repeated_int32 = 13;
repeated google.protobuf.Int64Value repeated_int64 = 14;
repeated google.protobuf.UInt32Value repeated_uint32 = 15;
repeated google.protobuf.UInt64Value repeated_uint64 = 16;
repeated google.protobuf.BoolValue repeated_bool = 17;
repeated google.protobuf.StringValue repeated_string = 18;
repeated google.protobuf.BytesValue repeated_bytes = 19;
// Wrappers in oneofs don't make sense, but we still need to make sure they
// work and don't crash.
oneof wrapper_oneof {
google.protobuf.DoubleValue oneof_double = 31;
google.protobuf.FloatValue oneof_float = 32;
google.protobuf.Int32Value oneof_int32 = 33;
google.protobuf.Int64Value oneof_int64 = 34;
google.protobuf.UInt32Value oneof_uint32 = 35;
google.protobuf.UInt64Value oneof_uint64 = 36;
google.protobuf.BoolValue oneof_bool = 37;
google.protobuf.StringValue oneof_string = 38;
google.protobuf.BytesValue oneof_bytes = 39;
string oneof_plain_string = 101;
}
}
message TimeMessage {
optional google.protobuf.Timestamp timestamp = 1;
optional google.protobuf.Duration duration = 2;
}
message Enumer {
optional TestEnum optional_enum = 11;
repeated TestEnum repeated_enum = 22;
optional string a_const = 3;
oneof a_oneof {
string str = 100;
TestEnum const = 101;
}
}
message MyRepeatedStruct {
repeated MyStruct structs = 1;
}
message MyStruct {
optional string string = 1;
optional google.protobuf.Struct struct = 2;
}

1983
deps/protobuf/ruby/tests/common_tests.rb vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,155 @@
#!/usr/bin/ruby
# generated_code.rb is in the same directory as this test.
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
require 'generated_code_pb'
require 'google/protobuf/well_known_types'
require 'test/unit'
def hex2bin(s)
s.scan(/../).map { |x| x.hex.chr }.join
end
class EncodeDecodeTest < Test::Unit::TestCase
def test_discard_unknown
# Test discard unknown in message.
unknown_msg = A::B::C::TestUnknown.new(:unknown_field => 1)
from = A::B::C::TestUnknown.encode(unknown_msg)
m = A::B::C::TestMessage.decode(from)
Google::Protobuf.discard_unknown(m)
to = A::B::C::TestMessage.encode(m)
assert_equal '', to
# Test discard unknown for singular message field.
unknown_msg = A::B::C::TestUnknown.new(
:optional_unknown =>
A::B::C::TestUnknown.new(:unknown_field => 1))
from = A::B::C::TestUnknown.encode(unknown_msg)
m = A::B::C::TestMessage.decode(from)
Google::Protobuf.discard_unknown(m)
to = A::B::C::TestMessage.encode(m.optional_msg)
assert_equal '', to
# Test discard unknown for repeated message field.
unknown_msg = A::B::C::TestUnknown.new(
:repeated_unknown =>
[A::B::C::TestUnknown.new(:unknown_field => 1)])
from = A::B::C::TestUnknown.encode(unknown_msg)
m = A::B::C::TestMessage.decode(from)
Google::Protobuf.discard_unknown(m)
to = A::B::C::TestMessage.encode(m.repeated_msg[0])
assert_equal '', to
# Test discard unknown for map value message field.
unknown_msg = A::B::C::TestUnknown.new(
:map_unknown =>
{"" => A::B::C::TestUnknown.new(:unknown_field => 1)})
from = A::B::C::TestUnknown.encode(unknown_msg)
m = A::B::C::TestMessage.decode(from)
Google::Protobuf.discard_unknown(m)
to = A::B::C::TestMessage.encode(m.map_string_msg[''])
assert_equal '', to
# Test discard unknown for oneof message field.
unknown_msg = A::B::C::TestUnknown.new(
:oneof_unknown =>
A::B::C::TestUnknown.new(:unknown_field => 1))
from = A::B::C::TestUnknown.encode(unknown_msg)
m = A::B::C::TestMessage.decode(from)
Google::Protobuf.discard_unknown(m)
to = A::B::C::TestMessage.encode(m.oneof_msg)
assert_equal '', to
end
def test_encode_json
msg = A::B::C::TestMessage.new({ optional_int32: 22 })
json = msg.to_json
to = A::B::C::TestMessage.decode_json(json)
assert_equal to.optional_int32, 22
msg = A::B::C::TestMessage.new({ optional_int32: 22 })
json = msg.to_json({ preserve_proto_fieldnames: true })
assert_match 'optional_int32', json
to = A::B::C::TestMessage.decode_json(json)
assert_equal 22, to.optional_int32
msg = A::B::C::TestMessage.new({ optional_int32: 22 })
json = A::B::C::TestMessage.encode_json(
msg,
{ preserve_proto_fieldnames: true, emit_defaults: true }
)
assert_match 'optional_int32', json
end
def test_encode_wrong_msg
assert_raise ::ArgumentError do
m = A::B::C::TestMessage.new(
:optional_int32 => 1,
)
Google::Protobuf::Any.encode(m)
end
end
def test_json_name
msg = A::B::C::TestJsonName.new(:value => 42)
json = msg.to_json
assert_match json, "{\"CustomJsonName\":42}"
end
def test_decode_depth_limit
msg = A::B::C::TestMessage.new(
optional_msg: A::B::C::TestMessage.new(
optional_msg: A::B::C::TestMessage.new(
optional_msg: A::B::C::TestMessage.new(
optional_msg: A::B::C::TestMessage.new(
optional_msg: A::B::C::TestMessage.new(
)
)
)
)
)
)
msg_encoded = A::B::C::TestMessage.encode(msg)
msg_out = A::B::C::TestMessage.decode(msg_encoded)
assert_match msg.to_json, msg_out.to_json
assert_raise Google::Protobuf::ParseError do
A::B::C::TestMessage.decode(msg_encoded, { recursion_limit: 4 })
end
msg_out = A::B::C::TestMessage.decode(msg_encoded, { recursion_limit: 5 })
assert_match msg.to_json, msg_out.to_json
end
def test_encode_depth_limit
msg = A::B::C::TestMessage.new(
optional_msg: A::B::C::TestMessage.new(
optional_msg: A::B::C::TestMessage.new(
optional_msg: A::B::C::TestMessage.new(
optional_msg: A::B::C::TestMessage.new(
optional_msg: A::B::C::TestMessage.new(
)
)
)
)
)
)
msg_encoded = A::B::C::TestMessage.encode(msg)
msg_out = A::B::C::TestMessage.decode(msg_encoded)
assert_match msg.to_json, msg_out.to_json
assert_raise RuntimeError do
A::B::C::TestMessage.encode(msg, { recursion_limit: 5 })
end
msg_encoded = A::B::C::TestMessage.encode(msg, { recursion_limit: 6 })
msg_out = A::B::C::TestMessage.decode(msg_encoded)
assert_match msg.to_json, msg_out.to_json
end
end

109
deps/protobuf/ruby/tests/gc_test.rb vendored Normal file
View File

@ -0,0 +1,109 @@
#!/usr/bin/ruby
#
# generated_code.rb is in the same directory as this test.
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
old_gc = GC.stress
# Ruby 2.7.0 - 2.7.1 has a GC bug in its parser, so turn off stress for now
# See https://bugs.ruby-lang.org/issues/16807
GC.stress = 0x01 | 0x04 unless RUBY_VERSION.match?(/^2\.7\./)
require 'generated_code_pb'
require 'generated_code_proto2_pb'
GC.stress = old_gc
require 'test/unit'
class GCTest < Test::Unit::TestCase
def get_msg_proto3
A::B::C::TestMessage.new(
:optional_int32 => 1,
:optional_int64 => 1,
:optional_uint32 => 1,
:optional_uint64 => 1,
:optional_bool => true,
:optional_double => 1.0,
:optional_float => 1.0,
:optional_string => "a",
:optional_bytes => "b",
:optional_enum => A::B::C::TestEnum::A,
:optional_msg => A::B::C::TestMessage.new(),
:repeated_int32 => [1],
:repeated_int64 => [1],
:repeated_uint32 => [1],
:repeated_uint64 => [1],
:repeated_bool => [true],
:repeated_double => [1.0],
:repeated_float => [1.0],
:repeated_string => ["a"],
:repeated_bytes => ["b"],
:repeated_enum => [A::B::C::TestEnum::A],
:repeated_msg => [A::B::C::TestMessage.new()],
:map_int32_string => {1 => "a"},
:map_int64_string => {1 => "a"},
:map_uint32_string => {1 => "a"},
:map_uint64_string => {1 => "a"},
:map_bool_string => {true => "a"},
:map_string_string => {"a" => "a"},
:map_string_msg => {"a" => A::B::C::TestMessage.new()},
:map_string_int32 => {"a" => 1},
:map_string_bool => {"a" => true},
)
end
def get_msg_proto2
A::B::Proto2::TestMessage.new(
:optional_int32 => 1,
:optional_int64 => 1,
:optional_uint32 => 1,
:optional_uint64 => 1,
:optional_bool => true,
:optional_double => 1.0,
:optional_float => 1.0,
:optional_string => "a",
:optional_bytes => "b",
:optional_enum => A::B::Proto2::TestEnum::A,
:optional_msg => A::B::Proto2::TestMessage.new(),
:repeated_int32 => [1],
:repeated_int64 => [1],
:repeated_uint32 => [1],
:repeated_uint64 => [1],
:repeated_bool => [true],
:repeated_double => [1.0],
:repeated_float => [1.0],
:repeated_string => ["a"],
:repeated_bytes => ["b"],
:repeated_enum => [A::B::Proto2::TestEnum::A],
:repeated_msg => [A::B::Proto2::TestMessage.new()],
:required_int32 => 1,
:required_int64 => 1,
:required_uint32 => 1,
:required_uint64 => 1,
:required_bool => true,
:required_double => 1.0,
:required_float => 1.0,
:required_string => "a",
:required_bytes => "b",
:required_enum => A::B::Proto2::TestEnum::A,
:required_msg => A::B::Proto2::TestMessage.new(),
)
end
def test_generated_msg
old_gc = GC.stress
GC.stress = 0x01 | 0x04
from = get_msg_proto3
data = A::B::C::TestMessage.encode(from)
to = A::B::C::TestMessage.decode(data)
# This doesn't work for proto2 on JRuby because there is a nested required message.
# A::B::Proto2::TestMessage has :required_msg which is of type:
# A::B::Proto2::TestMessage so there is no way to generate a valid
# message that doesn't exceed the depth limit
if !defined? JRUBY_VERSION
from = get_msg_proto2
data = A::B::Proto2::TestMessage.encode(from)
to = A::B::Proto2::TestMessage.decode(data)
end
GC.stress = old_gc
puts "passed"
end
end

View File

@ -0,0 +1,89 @@
syntax = "proto3";
package a.b.c;
message TestMessage {
int32 optional_int32 = 1;
int64 optional_int64 = 2;
uint32 optional_uint32 = 3;
uint64 optional_uint64 = 4;
bool optional_bool = 5;
double optional_double = 6;
float optional_float = 7;
string optional_string = 8;
bytes optional_bytes = 9;
TestEnum optional_enum = 10;
TestMessage optional_msg = 11;
repeated int32 repeated_int32 = 21;
repeated int64 repeated_int64 = 22;
repeated uint32 repeated_uint32 = 23;
repeated uint64 repeated_uint64 = 24;
repeated bool repeated_bool = 25;
repeated double repeated_double = 26;
repeated float repeated_float = 27;
repeated string repeated_string = 28;
repeated bytes repeated_bytes = 29;
repeated TestEnum repeated_enum = 30;
repeated TestMessage repeated_msg = 31;
oneof my_oneof {
int32 oneof_int32 = 41;
int64 oneof_int64 = 42;
uint32 oneof_uint32 = 43;
uint64 oneof_uint64 = 44;
bool oneof_bool = 45;
double oneof_double = 46;
float oneof_float = 47;
string oneof_string = 48;
bytes oneof_bytes = 49;
TestEnum oneof_enum = 50;
TestMessage oneof_msg = 51;
}
map<int32, string> map_int32_string = 61;
map<int64, string> map_int64_string = 62;
map<uint32, string> map_uint32_string = 63;
map<uint64, string> map_uint64_string = 64;
map<bool, string> map_bool_string = 65;
map<string, string> map_string_string = 66;
map<string, TestMessage> map_string_msg = 67;
map<string, TestEnum> map_string_enum = 68;
map<string, int32> map_string_int32 = 69;
map<string, bool> map_string_bool = 70;
message NestedMessage {
int32 foo = 1;
}
NestedMessage nested_message = 80;
// Reserved for non-existing field test.
// int32 non_exist = 89;
}
enum TestEnum {
Default = 0;
A = 1;
B = 2;
C = 3;
}
message testLowercaseNested {
message lowercase{}
}
message TestUnknown {
TestUnknown optional_unknown = 11;
repeated TestUnknown repeated_unknown = 31;
oneof my_oneof {
TestUnknown oneof_unknown = 51;
}
map<string, TestUnknown> map_unknown = 67;
int32 unknown_field = 89;
}
message TestJsonName {
int32 value = 1 [json_name = "CustomJsonName"];
}

View File

@ -0,0 +1,80 @@
syntax = "proto2";
package a.b.proto2;
message TestMessage {
optional int32 optional_int32 = 1;
optional int64 optional_int64 = 2;
optional uint32 optional_uint32 = 3;
optional uint64 optional_uint64 = 4;
optional bool optional_bool = 5;
optional double optional_double = 6;
optional float optional_float = 7;
optional string optional_string = 8;
optional bytes optional_bytes = 9;
optional TestEnum optional_enum = 10;
optional TestMessage optional_msg = 11;
repeated int32 repeated_int32 = 21;
repeated int64 repeated_int64 = 22;
repeated uint32 repeated_uint32 = 23;
repeated uint64 repeated_uint64 = 24;
repeated bool repeated_bool = 25;
repeated double repeated_double = 26;
repeated float repeated_float = 27;
repeated string repeated_string = 28;
repeated bytes repeated_bytes = 29;
repeated TestEnum repeated_enum = 30;
repeated TestMessage repeated_msg = 31;
required int32 required_int32 = 41;
required int64 required_int64 = 42;
required uint32 required_uint32 = 43;
required uint64 required_uint64 = 44;
required bool required_bool = 45;
required double required_double = 46;
required float required_float = 47;
required string required_string = 48;
required bytes required_bytes = 49;
required TestEnum required_enum = 50;
required TestMessage required_msg = 51;
oneof my_oneof {
int32 oneof_int32 = 61;
int64 oneof_int64 = 62;
uint32 oneof_uint32 = 63;
uint64 oneof_uint64 = 64;
bool oneof_bool = 65;
double oneof_double = 66;
float oneof_float = 67;
string oneof_string = 68;
bytes oneof_bytes = 69;
TestEnum oneof_enum = 70;
TestMessage oneof_msg = 71;
}
message NestedMessage {
optional int32 foo = 1;
}
optional NestedMessage nested_message = 80;
// Reserved for non-existing field test.
// int32 non_exist = 89;
}
enum TestEnum {
Default = 0;
A = 1;
B = 2;
C = 3;
}
message TestUnknown {
optional TestUnknown optional_unknown = 11;
repeated TestUnknown repeated_unknown = 31;
oneof my_oneof {
TestUnknown oneof_unknown = 51;
}
optional int32 unknown_field = 89;
}

View File

@ -0,0 +1,21 @@
#!/usr/bin/ruby
# generated_code.rb is in the same directory as this test.
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
require 'generated_code_proto2_pb'
require 'test_import_proto2_pb'
require 'test_ruby_package_proto2_pb'
require 'test/unit'
class GeneratedCodeProto2Test < Test::Unit::TestCase
def test_generated_msg
# just test that we can instantiate the message. The purpose of this test
# is to ensure that the output of the code generator is valid Ruby and
# successfully creates message definitions and classes, not to test every
# aspect of the extension (basic.rb is for that).
A::B::Proto2::TestMessage.new
FooBar::Proto2::TestImportedMessage.new
A::B::Proto2::TestRubyPackageMessage.new
end
end

View File

@ -0,0 +1,23 @@
#!/usr/bin/ruby
# generated_code.rb is in the same directory as this test.
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
require 'generated_code_pb'
require 'test_import_pb'
require 'test_ruby_package_pb'
require 'test/unit'
class GeneratedCodeTest < Test::Unit::TestCase
def test_generated_msg
# just test that we can instantiate the message. The purpose of this test
# is to ensure that the output of the code generator is valid Ruby and
# successfully creates message definitions and classes, not to test every
# aspect of the extension (basic.rb is for that).
A::B::C::TestMessage.new
A::B::C::TestMessage::NestedMessage.new
A::B::C::TestLowercaseNested::Lowercase.new
FooBar::TestImportedMessage.new
A::B::TestRubyPackageMessage.new
end
end

View File

@ -0,0 +1,19 @@
syntax = "proto3";
message Function {
string name = 1;
repeated Function.Parameter parameters = 2;
string return_type = 3;
message Parameter {
string name = 1;
Function.Parameter.Value value = 2;
message Value {
oneof type {
string string = 1;
int64 integer = 2;
}
}
}
}

View File

@ -0,0 +1,20 @@
#!/usr/bin/ruby
# multi_level_nesting_test_pb.rb is in the same directory as this test.
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
require 'test/unit'
require 'multi_level_nesting_test_pb'
#
# Provide tests for having messages nested 3 levels deep
#
class MultiLevelNestingTest < Test::Unit::TestCase
def test_levels_exist
assert ::Google::Protobuf::DescriptorPool.generated_pool.lookup("Function").msgclass
assert ::Google::Protobuf::DescriptorPool.generated_pool.lookup("Function.Parameter").msgclass
assert ::Google::Protobuf::DescriptorPool.generated_pool.lookup("Function.Parameter.Value").msgclass
end
end

View File

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

38
deps/protobuf/ruby/tests/stress.rb vendored Normal file
View File

@ -0,0 +1,38 @@
#!/usr/bin/ruby
require 'google/protobuf'
require 'test/unit'
module StressTest
pool = Google::Protobuf::DescriptorPool.new
pool.build do
add_message "TestMessage" do
optional :a, :int32, 1
repeated :b, :message, 2, "M"
end
add_message "M" do
optional :foo, :string, 1
end
end
TestMessage = pool.lookup("TestMessage").msgclass
M = pool.lookup("M").msgclass
class StressTest < Test::Unit::TestCase
def get_msg
TestMessage.new(:a => 1000,
:b => [M.new(:foo => "hello"),
M.new(:foo => "world")])
end
def test_stress
m = get_msg
data = TestMessage.encode(m)
100_000.times do
mnew = TestMessage.decode(data)
mnew = mnew.dup
assert_equal m.inspect, mnew.inspect
assert TestMessage.encode(mnew) == data
end
end
end
end

View File

@ -0,0 +1,5 @@
syntax = "proto3";
package foo_bar;
message TestImportedMessage {}

View File

@ -0,0 +1,5 @@
syntax = "proto2";
package foo_bar.proto2;
message TestImportedMessage {}

View File

@ -0,0 +1,7 @@
syntax = "proto3";
package foo_bar;
option ruby_package = "A::B";
message TestRubyPackageMessage {}

View File

@ -0,0 +1,7 @@
syntax = "proto2";
package foo_bar_proto2;
option ruby_package = "A::B::Proto2";
message TestRubyPackageMessage {}

176
deps/protobuf/ruby/tests/type_errors.rb vendored Normal file
View File

@ -0,0 +1,176 @@
#!/usr/bin/ruby
# generated_code.rb is in the same directory as this test.
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
require 'test/unit'
require 'google/protobuf/well_known_types'
require 'generated_code_pb'
class TestTypeErrors < Test::Unit::TestCase
# Ruby 2.4 unified Fixnum with Integer
IntegerType = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4') ? Fixnum : Integer
def test_bad_string
check_error Google::Protobuf::TypeError,
"Invalid argument for string field 'optional_string' (given #{IntegerType.name})." do
A::B::C::TestMessage.new(optional_string: 4)
end
check_error Google::Protobuf::TypeError,
"Invalid argument for string field 'oneof_string' (given #{IntegerType.name})." do
A::B::C::TestMessage.new(oneof_string: 4)
end
check_error ArgumentError,
"Expected array as initializer value for repeated field 'repeated_string' (given String)." do
A::B::C::TestMessage.new(repeated_string: '4')
end
end
def test_bad_float
check_error Google::Protobuf::TypeError,
"Expected number type for float field 'optional_float' (given TrueClass)." do
A::B::C::TestMessage.new(optional_float: true)
end
check_error Google::Protobuf::TypeError,
"Expected number type for float field 'oneof_float' (given TrueClass)." do
A::B::C::TestMessage.new(oneof_float: true)
end
check_error ArgumentError,
"Expected array as initializer value for repeated field 'repeated_float' (given String)." do
A::B::C::TestMessage.new(repeated_float: 'true')
end
end
def test_bad_double
check_error Google::Protobuf::TypeError,
"Expected number type for double field 'optional_double' (given Symbol)." do
A::B::C::TestMessage.new(optional_double: :double)
end
check_error Google::Protobuf::TypeError,
"Expected number type for double field 'oneof_double' (given Symbol)." do
A::B::C::TestMessage.new(oneof_double: :double)
end
check_error ArgumentError,
"Expected array as initializer value for repeated field 'repeated_double' (given FalseClass)." do
A::B::C::TestMessage.new(repeated_double: false)
end
end
def test_bad_bool
check_error Google::Protobuf::TypeError,
"Invalid argument for boolean field 'optional_bool' (given Float)." do
A::B::C::TestMessage.new(optional_bool: 4.4)
end
check_error Google::Protobuf::TypeError,
"Invalid argument for boolean field 'oneof_bool' (given Float)." do
A::B::C::TestMessage.new(oneof_bool: 4.4)
end
check_error ArgumentError,
"Expected array as initializer value for repeated field 'repeated_bool' (given String)." do
A::B::C::TestMessage.new(repeated_bool: 'hi')
end
end
def test_bad_int
check_error Google::Protobuf::TypeError,
"Expected number type for integral field 'optional_int32' (given String)." do
A::B::C::TestMessage.new(optional_int32: 'hi')
end
check_error RangeError,
"Non-integral floating point value assigned to integer field 'optional_int64' (given Float)." do
A::B::C::TestMessage.new(optional_int64: 2.4)
end
check_error Google::Protobuf::TypeError,
"Expected number type for integral field 'optional_uint32' (given Symbol)." do
A::B::C::TestMessage.new(optional_uint32: :thing)
end
check_error Google::Protobuf::TypeError,
"Expected number type for integral field 'optional_uint64' (given FalseClass)." do
A::B::C::TestMessage.new(optional_uint64: false)
end
check_error Google::Protobuf::TypeError,
"Expected number type for integral field 'oneof_int32' (given Symbol)." do
A::B::C::TestMessage.new(oneof_int32: :hi)
end
check_error RangeError,
"Non-integral floating point value assigned to integer field 'oneof_int64' (given Float)." do
A::B::C::TestMessage.new(oneof_int64: 2.4)
end
check_error Google::Protobuf::TypeError,
"Expected number type for integral field 'oneof_uint32' (given String)." do
A::B::C::TestMessage.new(oneof_uint32: 'x')
end
check_error RangeError,
"Non-integral floating point value assigned to integer field 'oneof_uint64' (given Float)." do
A::B::C::TestMessage.new(oneof_uint64: 1.1)
end
check_error ArgumentError,
"Expected array as initializer value for repeated field 'repeated_int32' (given Symbol)." do
A::B::C::TestMessage.new(repeated_int32: :hi)
end
check_error ArgumentError,
"Expected array as initializer value for repeated field 'repeated_int64' (given Float)." do
A::B::C::TestMessage.new(repeated_int64: 2.4)
end
check_error ArgumentError,
"Expected array as initializer value for repeated field 'repeated_uint32' (given String)." do
A::B::C::TestMessage.new(repeated_uint32: 'x')
end
check_error ArgumentError,
"Expected array as initializer value for repeated field 'repeated_uint64' (given Float)." do
A::B::C::TestMessage.new(repeated_uint64: 1.1)
end
end
def test_bad_enum
check_error RangeError,
"Unknown symbol value for enum field 'optional_enum'." do
A::B::C::TestMessage.new(optional_enum: 'enum')
end
check_error RangeError,
"Unknown symbol value for enum field 'oneof_enum'." do
A::B::C::TestMessage.new(oneof_enum: '')
end
check_error ArgumentError,
"Expected array as initializer value for repeated field 'repeated_enum' (given String)." do
A::B::C::TestMessage.new(repeated_enum: '')
end
end
def test_bad_bytes
check_error Google::Protobuf::TypeError,
"Invalid argument for bytes field 'optional_bytes' (given Float)." do
A::B::C::TestMessage.new(optional_bytes: 22.22)
end
check_error Google::Protobuf::TypeError,
"Invalid argument for bytes field 'oneof_bytes' (given Symbol)." do
A::B::C::TestMessage.new(oneof_bytes: :T22)
end
check_error ArgumentError,
"Expected array as initializer value for repeated field 'repeated_bytes' (given Symbol)." do
A::B::C::TestMessage.new(repeated_bytes: :T22)
end
end
def test_bad_msg
check_error Google::Protobuf::TypeError,
"Invalid type #{IntegerType.name} to assign to submessage field 'optional_msg'." do
A::B::C::TestMessage.new(optional_msg: 2)
end
check_error Google::Protobuf::TypeError,
"Invalid type String to assign to submessage field 'oneof_msg'." do
A::B::C::TestMessage.new(oneof_msg: '2')
end
check_error ArgumentError,
"Expected array as initializer value for repeated field 'repeated_msg' (given String)." do
A::B::C::TestMessage.new(repeated_msg: '2')
end
end
def check_error(type, message)
err = assert_raises type do
yield
end
assert_equal message, err.message
end
end

View File

@ -0,0 +1,247 @@
#!/usr/bin/ruby
require 'test/unit'
require 'google/protobuf/well_known_types'
class TestWellKnownTypes < Test::Unit::TestCase
def test_timestamp
ts = Google::Protobuf::Timestamp.new
assert_equal Time.at(0), ts.to_time
ts.seconds = 12345
assert_equal Time.at(12345), ts.to_time
assert_equal 12345, ts.to_i
# millisecond accuracy
time = Time.at(123456, 654321)
resp = ts.from_time(time)
assert_equal 123456, ts.seconds
assert_equal 654321000, ts.nanos
assert_equal time, ts.to_time
assert_equal resp, ts
# nanosecond accuracy
time = Time.at(123456, Rational(654321321, 1000))
resp = ts.from_time(time)
assert_equal 123456, ts.seconds
assert_equal 654321321, ts.nanos
assert_equal time, ts.to_time
assert_equal resp, ts
# Class based initialisation using from_time
time = Time.at(123456, Rational(654321321, 1000))
ts = Google::Protobuf::Timestamp.from_time(time)
assert_equal 123456, ts.seconds
assert_equal 654321321, ts.nanos
assert_equal time, ts.to_time
# Instance method returns the same value as class method
assert_equal Google::Protobuf::Timestamp.new.from_time(time),
Google::Protobuf::Timestamp.from_time(time)
end
def test_duration
duration = Google::Protobuf::Duration.new(seconds: 123, nanos: 456)
assert_equal 123.000000456, duration.to_f
end
def test_struct
struct = Google::Protobuf::Struct.new
substruct = {
"subkey" => 999,
"subkey2" => false
}
sublist = ["abc", 123, {"deepkey" => "deepval"}]
struct["number"] = 12345
struct["boolean-true"] = true
struct["boolean-false"] = false
struct["null"] = nil
struct["string"] = "abcdef"
struct["substruct"] = substruct
struct["sublist"] = sublist
assert_equal 12345, struct["number"]
assert_equal true, struct["boolean-true"]
assert_equal false, struct["boolean-false"]
assert_equal nil, struct["null"]
assert_equal "abcdef", struct["string"]
assert_equal(Google::Protobuf::Struct.from_hash(substruct),
struct["substruct"])
assert_equal(Google::Protobuf::ListValue.from_a(sublist),
struct["sublist"])
assert_equal true, struct.has_key?("null")
assert_equal false, struct.has_key?("missing_key")
should_equal = {
"number" => 12345,
"boolean-true" => true,
"boolean-false" => false,
"null" => nil,
"string" => "abcdef",
"substruct" => {
"subkey" => 999,
"subkey2" => false
},
"sublist" => ["abc", 123, {"deepkey" => "deepval"}]
}
list = struct["sublist"]
list.is_a?(Google::Protobuf::ListValue)
assert_equal "abc", list[0]
assert_equal 123, list[1]
assert_equal({"deepkey" => "deepval"}, list[2].to_h)
# to_h returns a fully-flattened Ruby structure (Hash and Array).
assert_equal(should_equal, struct.to_h)
# Test that we can safely access a missing key
assert_equal(nil, struct["missing_key"])
# Test that we can assign Struct and ListValue directly.
struct["substruct"] = Google::Protobuf::Struct.from_hash(substruct)
struct["sublist"] = Google::Protobuf::ListValue.from_a(sublist)
assert_equal(should_equal, struct.to_h)
struct["sublist"] << nil
should_equal["sublist"] << nil
assert_equal(should_equal, struct.to_h)
assert_equal(should_equal["sublist"].length, struct["sublist"].length)
assert_raise Google::Protobuf::UnexpectedStructType do
struct[123] = 5
end
assert_raise Google::Protobuf::UnexpectedStructType do
struct[5] = Time.new
end
assert_raise Google::Protobuf::UnexpectedStructType do
struct[5] = [Time.new]
end
assert_raise Google::Protobuf::UnexpectedStructType do
struct[5] = {123 => 456}
end
assert_raise Google::Protobuf::UnexpectedStructType do
struct = Google::Protobuf::Struct.new
struct.fields["foo"] = Google::Protobuf::Value.new
# Tries to return a Ruby value for a Value class whose type
# hasn't been filled in.
struct["foo"]
end
end
def test_any
ts = Google::Protobuf::Timestamp.new(seconds: 12345, nanos: 6789)
any = Google::Protobuf::Any.new
any.pack(ts)
assert any.is(Google::Protobuf::Timestamp)
assert_equal ts, any.unpack(Google::Protobuf::Timestamp)
any = Google::Protobuf::Any.pack(ts)
assert any.is(Google::Protobuf::Timestamp)
assert_equal ts, any.unpack(Google::Protobuf::Timestamp)
end
def test_struct_init
s = Google::Protobuf::Struct.new(fields: {'a' => Google::Protobuf::Value.new({number_value: 4.4})})
assert_equal 4.4, s['a']
s = Google::Protobuf::Struct.new(fields: {'a' => {number_value: 2.2}})
assert_equal 2.2, s['a']
s = Google::Protobuf::Struct.new(fields: {a: {number_value: 1.1}})
assert_equal 1.1, s[:a]
end
def test_struct_nested_init
s = Google::Protobuf::Struct.new(
fields: {
'a' => {string_value: 'A'},
'b' => {struct_value: {
fields: {
'x' => {list_value: {values: [{number_value: 1.0}, {string_value: "ok"}]}},
'y' => {bool_value: true}}}
},
'c' => {struct_value: {}}
}
)
assert_equal 'A', s['a']
assert_equal 'A', s[:a]
expected_b_x = [Google::Protobuf::Value.new(number_value: 1.0), Google::Protobuf::Value.new(string_value: "ok")]
assert_equal expected_b_x, s['b']['x'].values
assert_equal expected_b_x, s[:b][:x].values
assert_equal expected_b_x, s['b'][:x].values
assert_equal expected_b_x, s[:b]['x'].values
assert_equal true, s['b']['y']
assert_equal true, s[:b][:y]
assert_equal true, s[:b]['y']
assert_equal true, s['b'][:y]
assert_equal Google::Protobuf::Struct.new, s['c']
assert_equal Google::Protobuf::Struct.new, s[:c]
s = Google::Protobuf::Struct.new(
fields: {
a: {string_value: 'Eh'},
b: {struct_value: {
fields: {
y: {bool_value: false}}}
}
}
)
assert_equal 'Eh', s['a']
assert_equal 'Eh', s[:a]
assert_equal false, s['b']['y']
assert_equal false, s[:b][:y]
assert_equal false, s['b'][:y]
assert_equal false, s[:b]['y']
end
def test_b8325
value_field = Google::Protobuf::ListValue.descriptor.lookup("values")
proto = Google::Protobuf::ListValue.new(
values: [Google::Protobuf::Value.new(string_value: "Hello")]
)
assert_equal '[<Google::Protobuf::Value: string_value: "Hello">]', value_field.get(proto).inspect
end
def test_from_ruby
pb = Google::Protobuf::Value.from_ruby(nil)
assert_equal pb.null_value, :NULL_VALUE
pb = Google::Protobuf::Value.from_ruby(1.23)
assert_equal pb.number_value, 1.23
pb = Google::Protobuf::Value.from_ruby('1.23')
assert_equal pb.string_value, '1.23'
pb = Google::Protobuf::Value.from_ruby(true)
assert_equal pb.bool_value, true
pb = Google::Protobuf::Value.from_ruby(false)
assert_equal pb.bool_value, false
pb = Google::Protobuf::Value.from_ruby(Google::Protobuf::Struct.from_hash({ 'a' => 1, 'b' => '2', 'c' => [1, 2, 3], 'd' => nil, 'e' => true }))
assert_equal pb.struct_value, Google::Protobuf::Struct.from_hash({ 'a' => 1, 'b' => '2', 'c' => [1, 2, 3], 'd' => nil, 'e' => true })
pb = Google::Protobuf::Value.from_ruby({ 'a' => 1, 'b' => '2', 'c' => [1, 2, 3], 'd' => nil, 'e' => true })
assert_equal pb.struct_value, Google::Protobuf::Struct.from_hash({ 'a' => 1, 'b' => '2', 'c' => [1, 2, 3], 'd' => nil, 'e' => true })
pb = Google::Protobuf::Value.from_ruby(Google::Protobuf::ListValue.from_a([1, 2, 3]))
assert_equal pb.list_value, Google::Protobuf::ListValue.from_a([1, 2, 3])
pb = Google::Protobuf::Value.from_ruby([1, 2, 3])
assert_equal pb.list_value, Google::Protobuf::ListValue.from_a([1, 2, 3])
end
end

36
deps/protobuf/ruby/travis-test.sh vendored Normal file
View File

@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Exit on any error.
set -ex
test_version() {
version=$1
RUBY_CONFORMANCE=test_ruby
if [[ $version == jruby-9* ]] ; then
bash --login -c \
"rvm install $version && rvm use $version && rvm get head && \
which ruby && \
git clean -f && \
gem install --no-document bundler -v 2.3.26 && \ # Pin to bundler with ruby 2.5 support
bundle && \
rake test && \
rake gc_test && \
cd ../conformance && make test_jruby && \
cd ../ruby/compatibility_tests/v3.0.0 && ./test.sh"
else
bash --login -c \
"rvm install $version && rvm use $version && \
which ruby && \
git clean -f && \
gem install --no-document bundler -v 1.17.3 && bundle && \
rake test && \
rake gc_test && \
cd ../conformance && make ${RUBY_CONFORMANCE} && \
cd ../ruby/compatibility_tests/v3.0.0 && \
cp -R ../../lib lib && ./test.sh"
fi
}
test_version $1