protopuffs 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/.specification +108 -0
- data/LICENSE.txt +18 -0
- data/README.rdoc +74 -0
- data/Rakefile +49 -0
- data/VERSION.yml +4 -0
- data/lib/protopuffs.rb +31 -0
- data/lib/protopuffs/message/base.rb +124 -0
- data/lib/protopuffs/message/field.rb +226 -0
- data/lib/protopuffs/message/wire_type.rb +12 -0
- data/lib/protopuffs/parser/parser.rb +24 -0
- data/lib/protopuffs/parser/protocol_buffer.treetop +124 -0
- data/protopuffs.gemspec +78 -0
- data/test/abstract_syntax_tree_test.rb +153 -0
- data/test/fixtures/proto/person.proto +5 -0
- data/test/message_base_test.rb +202 -0
- data/test/message_field_test.rb +78 -0
- data/test/parse_tree_test.rb +208 -0
- data/test/protopuffs_test.rb +38 -0
- data/test/test_helper.rb +90 -0
- data/test/text_format_test.rb +23 -0
- data/test/wire_format_test.rb +293 -0
- metadata +113 -0
@@ -0,0 +1,78 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + '/test_helper'
|
4
|
+
|
5
|
+
class MessageFieldTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
context "creating a MessageField" do
|
8
|
+
should "not allow you to instantiate MessageField directly" do
|
9
|
+
assert_raises RuntimeError do
|
10
|
+
Protopuffs::MessageField.new "_", "_", "_", "_"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
should "complain if an invalid modifier is specified" do
|
15
|
+
["optional", "required", "repeated"].each do |modifier|
|
16
|
+
assert_nothing_raised {Protopuffs::Int32.new(modifier, 'identitifer', 1)}
|
17
|
+
end
|
18
|
+
assert_raises(ArgumentError) {Protopuffs::Int32.new("invalid_modifier", "identifier", 1)}
|
19
|
+
end
|
20
|
+
|
21
|
+
should "instantiate the right class, given a type string" do
|
22
|
+
[[ "int32", Protopuffs::Int32],
|
23
|
+
[ "int64", Protopuffs::Int64],
|
24
|
+
[ "uint32", Protopuffs::UInt32],
|
25
|
+
[ "uint64", Protopuffs::UInt64],
|
26
|
+
[ "bool", Protopuffs::Bool],
|
27
|
+
[ "double", Protopuffs::Double],
|
28
|
+
[ "fixed64", Protopuffs::Fixed64],
|
29
|
+
[ "string", Protopuffs::String],
|
30
|
+
[ "bytes", Protopuffs::Bytes],
|
31
|
+
[ "float", Protopuffs::Float],
|
32
|
+
[ "fixed32", Protopuffs::Fixed32],
|
33
|
+
[ "sfixed32", Protopuffs::SFixed32],
|
34
|
+
[ "embedded", Protopuffs::Embedded],
|
35
|
+
].each do |type, klass|
|
36
|
+
assert_kind_of klass, Protopuffs::MessageField.factory(type, "optional", "a_string", 1, 2)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
should "set a string's default to '' when a default isn't specified" do
|
41
|
+
field = Protopuffs::String.new("optional", "name", 1)
|
42
|
+
assert_equal "", field.default
|
43
|
+
end
|
44
|
+
|
45
|
+
should "set a string's default to '' when a default is specified as nil" do
|
46
|
+
field = Protopuffs::String.new("optional", "name", 1, nil)
|
47
|
+
assert_equal "", field.default
|
48
|
+
end
|
49
|
+
|
50
|
+
should "set a numeric's default to 0 when a default isn't specified or is specified as nil" do
|
51
|
+
numeric_types = [Protopuffs::Double, Protopuffs::Float,
|
52
|
+
Protopuffs::Int32, Protopuffs::Int64,
|
53
|
+
Protopuffs::UInt32, Protopuffs::UInt64,
|
54
|
+
Protopuffs::Fixed32, Protopuffs::Fixed64,
|
55
|
+
Protopuffs::SFixed32]
|
56
|
+
numeric_types.each do |type|
|
57
|
+
assert_equal 0, type.new("optional", "number", 1).default
|
58
|
+
assert_equal 0, type.new("optional", "number", 1, nil).default
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
should "set a bool's default to false when a default isn't specified" do
|
63
|
+
field = Protopuffs::Bool.new("optional", "opt_in", 1)
|
64
|
+
assert_same false, field.default
|
65
|
+
end
|
66
|
+
|
67
|
+
should "set a bool's default to false when a default is specified as nil" do
|
68
|
+
field = Protopuffs::Bool.new("optional", "opt_in", 1, nil)
|
69
|
+
assert_same false, field.default
|
70
|
+
end
|
71
|
+
|
72
|
+
should "set the default to 'Matz' when that default is specified" do
|
73
|
+
field = Protopuffs::String.new("optional", "name", 1, "Matz")
|
74
|
+
assert_equal "Matz", field.default
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + '/test_helper'
|
4
|
+
|
5
|
+
class ParseTreeTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
context "a protocol buffer descriptor" do
|
8
|
+
setup do
|
9
|
+
@parser = Protopuffs::Parser::ProtocolBufferDescriptor.new
|
10
|
+
end
|
11
|
+
|
12
|
+
context "with an empty Person message" do
|
13
|
+
setup do
|
14
|
+
@proto = @parser.parse("message Person {}")
|
15
|
+
end
|
16
|
+
|
17
|
+
should "have one message" do
|
18
|
+
assert_equal 1, @proto.messages.size
|
19
|
+
end
|
20
|
+
|
21
|
+
should "have a message named Person" do
|
22
|
+
assert_equal "Person", @proto.messages.first.name
|
23
|
+
end
|
24
|
+
|
25
|
+
should "have an empty body" do
|
26
|
+
assert @proto.messages.first.body.empty?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "with two empty messages Apple and Orange" do
|
31
|
+
setup do
|
32
|
+
@descriptor = <<-proto
|
33
|
+
message Apple {}
|
34
|
+
message Orange {}
|
35
|
+
proto
|
36
|
+
end
|
37
|
+
|
38
|
+
should "have two messages" do
|
39
|
+
proto = @parser.parse(@descriptor)
|
40
|
+
assert_equal 2, proto.messages.size
|
41
|
+
end
|
42
|
+
|
43
|
+
should "have messages named Apple and Orange" do
|
44
|
+
proto = @parser.parse(@descriptor)
|
45
|
+
assert_equal %w(Apple Orange), proto.messages.map { |m| m.name }.sort
|
46
|
+
end
|
47
|
+
|
48
|
+
should "have messages with empty bodes" do
|
49
|
+
proto = @parser.parse(@descriptor)
|
50
|
+
assert proto.messages.all? { |m| m.body.empty? }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "a proto with a Person message including a name field" do
|
55
|
+
setup do
|
56
|
+
@descriptor = <<-proto
|
57
|
+
message Person {
|
58
|
+
required string name = 1;
|
59
|
+
}
|
60
|
+
proto
|
61
|
+
end
|
62
|
+
|
63
|
+
should "have one message named person" do
|
64
|
+
proto = @parser.parse(@descriptor)
|
65
|
+
assert_equal 1, proto.messages.size
|
66
|
+
assert_equal "Person", proto.messages.first.name
|
67
|
+
end
|
68
|
+
|
69
|
+
should "have one required string field called name with tag 1" do
|
70
|
+
proto = @parser.parse(@descriptor)
|
71
|
+
fields = proto.messages.first.body.fields
|
72
|
+
assert_equal 1, fields.size
|
73
|
+
assert_equal "required", fields.first.modifier.text_value
|
74
|
+
assert_equal "string", fields.first.type.text_value
|
75
|
+
assert_equal "name", fields.first.identifier.text_value
|
76
|
+
assert_equal "1", fields.first.integer.text_value
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "with a Person message including three fields" do
|
81
|
+
setup do
|
82
|
+
@descriptor = <<-proto
|
83
|
+
message Person {
|
84
|
+
required string name = 1;
|
85
|
+
required int32 id = 2;
|
86
|
+
optional string email = 3;
|
87
|
+
}
|
88
|
+
proto
|
89
|
+
end
|
90
|
+
|
91
|
+
should "have one message named person" do
|
92
|
+
proto = @parser.parse(@descriptor)
|
93
|
+
assert_equal 1, proto.messages.size
|
94
|
+
assert_equal "Person", proto.messages.first.name
|
95
|
+
end
|
96
|
+
|
97
|
+
should "have three fields with correct components" do
|
98
|
+
proto = @parser.parse(@descriptor)
|
99
|
+
fields = proto.messages.first.body.fields
|
100
|
+
assert_equal 3, fields.size
|
101
|
+
actual = fields.map { |f| [f.modifier, f.type, f.identifier, f.integer].map { |el| el.text_value } }
|
102
|
+
expected = [ %w(required string name 1),
|
103
|
+
%w(required int32 id 2),
|
104
|
+
%w(optional string email 3) ]
|
105
|
+
assert_equal expected, actual
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context "with a Person message including two fields with defaults and one without" do
|
110
|
+
setup do
|
111
|
+
@proto = @parser.parse(<<-proto)
|
112
|
+
message Person {
|
113
|
+
required string name = 1;
|
114
|
+
optional string language = 2 [default = "en"];
|
115
|
+
optional int32 account_code = 3 [default = 0];
|
116
|
+
}
|
117
|
+
proto
|
118
|
+
end
|
119
|
+
|
120
|
+
should "have one message named person" do
|
121
|
+
assert_equal 1, @proto.messages.size
|
122
|
+
assert_equal "Person", @proto.messages.first.name
|
123
|
+
end
|
124
|
+
|
125
|
+
should "have three fields with correct components" do
|
126
|
+
fields = @proto.messages.first.body.fields
|
127
|
+
assert_equal 3, fields.size
|
128
|
+
actual = fields.map { |f| [f.modifier, f.type, f.identifier, f.integer, f.default] }
|
129
|
+
actual.map! { |f| f.map! { |el| el.respond_to?(:text_value) ? el.text_value : el } }
|
130
|
+
expected = [ ["required", "string", "name", "1", nil],
|
131
|
+
["optional", "string", "language", "2", "en"],
|
132
|
+
["optional", "int32", "account_code", "3", 0] ]
|
133
|
+
assert_equal expected, actual
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context "with a message including lots of comments" do
|
138
|
+
setup do
|
139
|
+
@proto = @parser.parse(<<-proto)
|
140
|
+
// test
|
141
|
+
//test
|
142
|
+
message Person { //test // test
|
143
|
+
// test
|
144
|
+
required string name = 1; // test
|
145
|
+
// optional string language = 2 [default = "en"];
|
146
|
+
optional int32 account_code = 3 [default = 0]; // test
|
147
|
+
// test
|
148
|
+
} // test
|
149
|
+
// test
|
150
|
+
//test
|
151
|
+
proto
|
152
|
+
end
|
153
|
+
|
154
|
+
should "parse" do
|
155
|
+
assert_not_nil @proto
|
156
|
+
end
|
157
|
+
|
158
|
+
should "have uncommented fields with correct components" do
|
159
|
+
fields = @proto.messages.first.body.fields
|
160
|
+
assert_equal 2, fields.size
|
161
|
+
actual = fields.map { |f| [f.modifier, f.type, f.identifier, f.integer, f.default] }
|
162
|
+
actual.map! { |f| f.map! { |el| el.respond_to?(:text_value) ? el.text_value : el } }
|
163
|
+
expected = [ ["required", "string", "name", "1", nil],
|
164
|
+
["optional", "int32", "account_code", "3", 0] ]
|
165
|
+
assert_equal expected, actual
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
context "with a message including a user-typed field" do
|
171
|
+
setup do
|
172
|
+
@proto = @parser.parse(<<-proto)
|
173
|
+
message Person {
|
174
|
+
required string name = 1;
|
175
|
+
repeated Address addresses = 2;
|
176
|
+
}
|
177
|
+
proto
|
178
|
+
end
|
179
|
+
|
180
|
+
should "parse" do
|
181
|
+
assert_not_nil @proto
|
182
|
+
end
|
183
|
+
|
184
|
+
should "have two fields with correct components" do
|
185
|
+
fields = @proto.messages.first.body.fields
|
186
|
+
assert_equal 2, fields.size
|
187
|
+
actual = fields.map { |f| [f.modifier, f.type, f.identifier, f.integer] }
|
188
|
+
actual.map! { |f| f.map! { |el| el.respond_to?(:text_value) ? el.text_value : el } }
|
189
|
+
expected = [ ["required", "string", "name", "1"],
|
190
|
+
["repeated", "Address", "addresses", "2"] ]
|
191
|
+
assert_equal expected, actual
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
should "raise a ParseError when parsing a message with a syntax error" do
|
197
|
+
assert_raises Protopuffs::ParseError do
|
198
|
+
@parser.parse(<<-proto)
|
199
|
+
message Person {
|
200
|
+
required name = 1
|
201
|
+
}
|
202
|
+
proto
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + '/test_helper'
|
4
|
+
|
5
|
+
class ProtopuffsTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
context ".proto_load_path accessors" do
|
8
|
+
setup { Protopuffs.proto_load_path = [] }
|
9
|
+
|
10
|
+
should "have an accessor for an array of load paths for .proto files" do
|
11
|
+
Protopuffs.proto_load_path << "proto_files" << "other_proto_files"
|
12
|
+
assert_equal ["proto_files", "other_proto_files"], Protopuffs.proto_load_path
|
13
|
+
end
|
14
|
+
|
15
|
+
should "have a mutator for directly assigning the load paths" do
|
16
|
+
Protopuffs.proto_load_path = ["my_proto_files"]
|
17
|
+
assert_equal ["my_proto_files"], Protopuffs.proto_load_path
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
should "have a ParseError class" do
|
22
|
+
Protopuffs::ParseError
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
context ".load_message_classes when a descriptor is in the load path with a Person message" do
|
27
|
+
setup { Protopuffs.proto_load_path = ["#{File.dirname(__FILE__)}/fixtures/proto"] }
|
28
|
+
|
29
|
+
should "create a Person message class with correct accessors" do
|
30
|
+
Protopuffs.load_message_classes
|
31
|
+
p = Protopuffs::Message::Person.new
|
32
|
+
p.name = "Chris"
|
33
|
+
p.id = 42
|
34
|
+
p.email = "chris@kampers.net"
|
35
|
+
assert_equal ["Chris", 42, "chris@kampers.net"], [p.name, p.id, p.email]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'test/unit'
|
5
|
+
|
6
|
+
gem "polyglot", "=0.2.9"
|
7
|
+
gem "treetop", "=1.4.2"
|
8
|
+
|
9
|
+
gem "thoughtbot-shoulda", "=2.10.2"
|
10
|
+
require 'shoulda'
|
11
|
+
|
12
|
+
gem "mocha", "=0.9.8"
|
13
|
+
require 'mocha'
|
14
|
+
|
15
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
16
|
+
require 'protopuffs'
|
17
|
+
|
18
|
+
class Test::Unit::TestCase
|
19
|
+
|
20
|
+
# helper for debugging
|
21
|
+
def print_bytes(string)
|
22
|
+
puts
|
23
|
+
string.each_byte do |byte|
|
24
|
+
printf("%1$08b (%1$02X)\n", byte)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# helper for generating test names
|
29
|
+
def self.inspect_bytes(bytes_array)
|
30
|
+
"[#{bytes_array.map { |b| sprintf("%02X", b) }.join(' ')}]"
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def self.should_encode_wire_format_from_fields(expected_bytes, actual_fields)
|
35
|
+
should "encode the fields #{actual_fields.inspect} to the byte string #{inspect_bytes(expected_bytes)}" do
|
36
|
+
actual_fields.each_pair do |name, value|
|
37
|
+
value = value.call if value.respond_to?(:call)
|
38
|
+
@message.send("#{name}=", value)
|
39
|
+
end
|
40
|
+
actual_bytes = @message.to_wire_format.unpack('C*')
|
41
|
+
assert_equal expected_bytes, actual_bytes
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.should_decode_wire_format_to_fields(actual_bytes, expected_fields)
|
46
|
+
should "decode the byte string #{inspect_bytes(actual_bytes)} to the fields #{expected_fields.inspect}" do
|
47
|
+
buffer = StringIO.new(actual_bytes.pack('C*'))
|
48
|
+
buffer.set_encoding("BINARY") if buffer.respond_to?(:set_encoding)
|
49
|
+
@message.from_wire_format(buffer)
|
50
|
+
actual_fields = @message.class.fields.inject({}) { |hash, field|
|
51
|
+
hash[field.identifier.to_sym] = @message.send(field.identifier)
|
52
|
+
hash
|
53
|
+
}
|
54
|
+
|
55
|
+
expected_fields.each_pair do |key, expected_value|
|
56
|
+
expected_value = expected_value.call if expected_value.respond_to?(:call)
|
57
|
+
if expected_value.is_a?(Float)
|
58
|
+
assert_in_delta(expected_value, actual_fields[key], Float::EPSILON)
|
59
|
+
else
|
60
|
+
assert_equal expected_value, actual_fields[key]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
assert_equal expected_fields.size, actual_fields.size
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.should_encode_and_decode_wire_format_and_fields(bytes, fields)
|
69
|
+
self.should_encode_wire_format_from_fields(bytes, fields)
|
70
|
+
self.should_decode_wire_format_to_fields(bytes, fields)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.should_losslessly_encode_and_decode_a_random_sample(fields)
|
74
|
+
raise ArgumentError if fields.size > 1
|
75
|
+
name, range = fields.shift
|
76
|
+
should "get the same value back after encoding and decoding a random sample of the values #{range.inspect} for field #{name.inspect}" do
|
77
|
+
size = range.last - range.first
|
78
|
+
size -= 1 if range.exclude_end?
|
79
|
+
values_to_test = [range.first, range.first + size]
|
80
|
+
250.times { values_to_test << range.first + rand(size) }
|
81
|
+
values_to_test.each do |value|
|
82
|
+
@message.send("#{name}=", value)
|
83
|
+
@message.from_wire_format(@message.to_wire_format)
|
84
|
+
assert_equal value, @message.send(name)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + '/test_helper'
|
4
|
+
|
5
|
+
class TextFormatTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
context "a message called Test1 with one int32 field" do
|
8
|
+
# from http://code.google.com/apis/protocolbuffers/docs/overview.html#whynotxml
|
9
|
+
setup do
|
10
|
+
fields = [Protopuffs::String.new("required", "name", 1),
|
11
|
+
Protopuffs::String.new("required", "email", 2)]
|
12
|
+
Protopuffs::Message::Base.define_message_class("Person", fields)
|
13
|
+
@message = Protopuffs::Message::Person.new
|
14
|
+
@message.name = "John Doe"
|
15
|
+
@message.email = "jdoe@example.com"
|
16
|
+
end
|
17
|
+
|
18
|
+
should "return the correct text format from #inspect" do
|
19
|
+
assert_equal %Q(person {\n name: "John Doe"\n email: "jdoe@example.com"\n}), @message.inspect
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,293 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require File.dirname(__FILE__) + '/test_helper'
|
4
|
+
|
5
|
+
class WireFormatTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
context "a message with one int32 field tagged #1" do
|
8
|
+
# from http://code.google.com/apis/protocolbuffers/docs/encoding.html#simple
|
9
|
+
setup do
|
10
|
+
fields = [Protopuffs::Int32.new("required", "a", 1)]
|
11
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
12
|
+
@message = Protopuffs::Message::Test1.new
|
13
|
+
end
|
14
|
+
|
15
|
+
should_encode_wire_format_from_fields [0x08, 0x96, 0x01], :a => 150
|
16
|
+
should_decode_wire_format_to_fields [0x08, 0x96, 0x01], :a => 150
|
17
|
+
|
18
|
+
# should ignore unknown fields: this message also has an int32 tagged #2 with value 157,372
|
19
|
+
should_decode_wire_format_to_fields [0x08, 0x96, 0x01, 0x10, 0xBC, 0xCD, 0x09], :a => 150
|
20
|
+
|
21
|
+
should "return itself from #from_wire_format" do
|
22
|
+
wire_message = StringIO.new([0x08, 0x96, 0x01].pack('C*'))
|
23
|
+
assert_same @message, @message.from_wire_format(wire_message)
|
24
|
+
end
|
25
|
+
|
26
|
+
should "accept a string as an argument to #from_wire_format" do
|
27
|
+
wire_message = [0x08, 0x96, 0x01].pack('C*')
|
28
|
+
@message.from_wire_format(wire_message)
|
29
|
+
assert_equal 150, @message.a
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
context "a message with two int32 fields tagged #1 and #2" do
|
35
|
+
setup do
|
36
|
+
fields = [Protopuffs::Int32.new("required", "a", 1),
|
37
|
+
Protopuffs::Int32.new("required", "b", 2)]
|
38
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
39
|
+
@message = Protopuffs::Message::Test1.new
|
40
|
+
end
|
41
|
+
|
42
|
+
should_encode_wire_format_from_fields [0x08, 0x96, 0x01, 0x10, 0xBC, 0xCD, 0x09],
|
43
|
+
:a => 150, :b => 157_372
|
44
|
+
should_decode_wire_format_to_fields [0x08, 0x96, 0x01, 0x10, 0xBC, 0xCD, 0x09],
|
45
|
+
:a => 150, :b => 157_372
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
context "a message with one int64 field tagged #1" do
|
50
|
+
setup do
|
51
|
+
fields = [Protopuffs::Int64.new("required", "a", 1)]
|
52
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
53
|
+
@message = Protopuffs::Message::Test1.new
|
54
|
+
end
|
55
|
+
|
56
|
+
should_encode_wire_format_from_fields [0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02],
|
57
|
+
:a => 2**50
|
58
|
+
should_decode_wire_format_to_fields [0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02],
|
59
|
+
:a => 2**50
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
context "a message with one uint32 field tagged #1 and one uint64 field tagged #2" do
|
64
|
+
setup do
|
65
|
+
fields = [Protopuffs::UInt32.new("required", "a", 1),
|
66
|
+
Protopuffs::UInt64.new("required", "b", 2)]
|
67
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
68
|
+
@message = Protopuffs::Message::Test1.new
|
69
|
+
end
|
70
|
+
|
71
|
+
should_encode_wire_format_from_fields [0x08, 0x90, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x20],
|
72
|
+
:a => 912, :b => 2**54
|
73
|
+
should_decode_wire_format_to_fields [0x08, 0x90, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x20],
|
74
|
+
:a => 912, :b => 2**54
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
context "a message with two bool fields tagged #1 and #2" do
|
79
|
+
setup do
|
80
|
+
fields = [Protopuffs::Bool.new("required", "a", 1),
|
81
|
+
Protopuffs::Bool.new("required", "b", 2)]
|
82
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
83
|
+
@message = Protopuffs::Message::Test1.new
|
84
|
+
end
|
85
|
+
|
86
|
+
should_encode_wire_format_from_fields [0x08, 0x00, 0x10, 0x01],
|
87
|
+
:a => false, :b => true
|
88
|
+
should_decode_wire_format_to_fields [0x08, 0x00, 0x10, 0x01],
|
89
|
+
:a => false, :b => true
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
context "a message with one string field tagged #2" do
|
94
|
+
setup do
|
95
|
+
# from http://code.google.com/apis/protocolbuffers/docs/encoding.html#types
|
96
|
+
fields = [Protopuffs::String.new("required", "b", 2)]
|
97
|
+
Protopuffs::Message::Base.define_message_class("Test2", fields)
|
98
|
+
@message = Protopuffs::Message::Test2.new
|
99
|
+
end
|
100
|
+
|
101
|
+
should_encode_wire_format_from_fields [0x12, 0x07, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6E, 0x67],
|
102
|
+
:b => "testing"
|
103
|
+
should_decode_wire_format_to_fields [0x12, 0x07, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6E, 0x67],
|
104
|
+
:b => "testing"
|
105
|
+
|
106
|
+
should_encode_wire_format_from_fields [0x12, 0x01, 0x32], :b => 2
|
107
|
+
should_decode_wire_format_to_fields [0x12, 0x01, 0x32], :b => "2"
|
108
|
+
|
109
|
+
should_encode_wire_format_from_fields [0x12, 0x0C, 0xE3, 0x81, 0x93, 0xE3, 0x81, 0xAB, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x8F],
|
110
|
+
:b => "こにちわ"
|
111
|
+
should_decode_wire_format_to_fields [0x12, 0x0C, 0xE3, 0x81, 0x93, 0xE3, 0x81, 0xAB, 0xE3, 0x81, 0xA1, 0xE3, 0x82, 0x8F],
|
112
|
+
:b => "こにちわ"
|
113
|
+
|
114
|
+
should_encode_wire_format_from_fields [0x12, 0x05, 0xD2, 0x90, 0x41, 0xC3, 0x9C],
|
115
|
+
:b => "ҐAÜ"
|
116
|
+
should_decode_wire_format_to_fields [0x12, 0x05, 0xD2, 0x90, 0x41, 0xC3, 0x9C],
|
117
|
+
:b => "ҐAÜ"
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
context "a message with a bytes field tagged #1" do
|
122
|
+
setup do
|
123
|
+
fields = [Protopuffs::Bytes.new("required", "a", 1)]
|
124
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
125
|
+
@message = Protopuffs::Message::Test1.new
|
126
|
+
end
|
127
|
+
|
128
|
+
should_encode_wire_format_from_fields [0x0A, 0x04, 0xDE, 0xCA, 0xFB, 0xAD],
|
129
|
+
:a => [0xDE, 0xCA, 0xFB, 0xAD].pack('C*')
|
130
|
+
should_decode_wire_format_to_fields [0x0A, 0x04, 0xDE, 0xCA, 0xFB, 0xAD],
|
131
|
+
:a => [0xDE, 0xCA, 0xFB, 0xAD].pack('C*')
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
context "a message with one float field tagged #1" do
|
136
|
+
setup do
|
137
|
+
fields = [Protopuffs::Float.new("required", "a", 1)]
|
138
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
139
|
+
@message = Protopuffs::Message::Test1.new
|
140
|
+
end
|
141
|
+
|
142
|
+
# 1.61803 gives you repeating binary digits when encoded, so the number
|
143
|
+
# you get from decoding is different (within Float::EPSILON)
|
144
|
+
should_encode_wire_format_from_fields [0x0D, 0x9B, 0x1B, 0xCF, 0x3F],
|
145
|
+
:a => 1.61803
|
146
|
+
should_decode_wire_format_to_fields [0x0D, 0x9B, 0x1B, 0xCF, 0x3F],
|
147
|
+
:a => 1.6180299520492554
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
context "a message with one double field tagged #1" do
|
152
|
+
setup do
|
153
|
+
fields = [Protopuffs::Double.new("required", "a", 1)]
|
154
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
155
|
+
@message = Protopuffs::Message::Test1.new
|
156
|
+
end
|
157
|
+
|
158
|
+
# 64-bit doubles have enough precision to encode/decode 1.61803,
|
159
|
+
# unlike the 32-bit floats above
|
160
|
+
should_encode_wire_format_from_fields [0x09, 0x6C, 0x26, 0xDF, 0x6C, 0x73, 0xE3, 0xF9, 0x3F],
|
161
|
+
:a => 1.61803
|
162
|
+
should_decode_wire_format_to_fields [0x09, 0x6C, 0x26, 0xDF, 0x6C, 0x73, 0xE3, 0xF9, 0x3F],
|
163
|
+
:a => 1.61803
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
context "a message with one fixed64 field tagged #1" do
|
168
|
+
setup do
|
169
|
+
fields = [Protopuffs::Fixed64.new("required", "a", 1)]
|
170
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
171
|
+
@message = Protopuffs::Message::Test1.new
|
172
|
+
end
|
173
|
+
|
174
|
+
should_encode_wire_format_from_fields [0x09, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F],
|
175
|
+
:a => 2**62 - 15
|
176
|
+
should_decode_wire_format_to_fields [0x09, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F],
|
177
|
+
:a => 2**62 - 15
|
178
|
+
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
context "a message with one fixed32 field tagged #1" do
|
183
|
+
setup do
|
184
|
+
fields = [Protopuffs::Fixed32.new("required", "a", 1)]
|
185
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
186
|
+
@message = Protopuffs::Message::Test1.new
|
187
|
+
end
|
188
|
+
|
189
|
+
should_encode_wire_format_from_fields [0x0D, 0xFE, 0xFF, 0xFF, 0xFF],
|
190
|
+
:a => 2**32 - 2
|
191
|
+
should_decode_wire_format_to_fields [0x0D, 0xFE, 0xFF, 0xFF, 0xFF],
|
192
|
+
:a => 2**32 - 2
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
context "a message with one sfixed32 field tagged #1" do
|
197
|
+
setup do
|
198
|
+
fields = [Protopuffs::SFixed32.new("required", "a", 1)]
|
199
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
200
|
+
@message = Protopuffs::Message::Test1.new
|
201
|
+
end
|
202
|
+
|
203
|
+
should_encode_and_decode_wire_format_and_fields [0x0D, 0x05, 0x00, 0x00, 0x80],
|
204
|
+
:a => -2**31 + 5
|
205
|
+
should_encode_and_decode_wire_format_and_fields [0x0D, 0x05, 0x80, 0x00, 0x00],
|
206
|
+
:a => 2**15 + 5
|
207
|
+
should_losslessly_encode_and_decode_a_random_sample :a => -2**31...2**31
|
208
|
+
end
|
209
|
+
|
210
|
+
|
211
|
+
context "a message with one repeating int32 field tagged #1" do
|
212
|
+
setup do
|
213
|
+
fields = [Protopuffs::Int32.new("repeated", "a", 1)]
|
214
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
215
|
+
@message = Protopuffs::Message::Test1.new
|
216
|
+
end
|
217
|
+
|
218
|
+
should_encode_wire_format_from_fields [0x08, 0x96, 0x01, 0x08, 0xBC, 0xCD, 0x09, 0x08, 0x3D],
|
219
|
+
:a => [150, 157_372, 61]
|
220
|
+
should_decode_wire_format_to_fields [0x08, 0x96, 0x01, 0x08, 0xBC, 0xCD, 0x09, 0x08, 0x3D],
|
221
|
+
:a => [150, 157_372, 61]
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
context "a message with one embedded-message field Test1 tagged #3 (where Test1 has an int32 field tagged #1)" do
|
226
|
+
# from http://code.google.com/apis/protocolbuffers/docs/encoding.html#embedded
|
227
|
+
setup do
|
228
|
+
test1_fields = [Protopuffs::Int32.new("required", "a", 1)]
|
229
|
+
Protopuffs::Message::Base.define_message_class("Test1", test1_fields)
|
230
|
+
|
231
|
+
test3_fields = [Protopuffs::Embedded.new("Test1", "required", "c", 3)]
|
232
|
+
Protopuffs::Message::Base.define_message_class("Test3", test3_fields)
|
233
|
+
@message = Protopuffs::Message::Test3.new
|
234
|
+
end
|
235
|
+
|
236
|
+
should_encode_wire_format_from_fields [0x1A, 0x03, 0x08, 0x96, 0x01],
|
237
|
+
:c => lambda { msg = Protopuffs::Message::Test1.new; msg.a = 150; msg }
|
238
|
+
should_decode_wire_format_to_fields [0x1A, 0x03, 0x08, 0x96, 0x01],
|
239
|
+
:c => lambda { msg = Protopuffs::Message::Test1.new; msg.a = 150; msg }
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
context "a message with two int32 fields tagged #1 (optional, default=150) and #2 (required)" do
|
244
|
+
setup do
|
245
|
+
fields = [Protopuffs::Int32.new("optional", "a", 1, 150),
|
246
|
+
Protopuffs::Int32.new("required", "b", 2)]
|
247
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
248
|
+
@message = Protopuffs::Message::Test1.new
|
249
|
+
end
|
250
|
+
|
251
|
+
should_encode_wire_format_from_fields [0x10, 0xBC, 0xCD, 0x09],
|
252
|
+
:b => 157_372
|
253
|
+
should_decode_wire_format_to_fields [0x10, 0xBC, 0xCD, 0x09],
|
254
|
+
:a => 150, :b => 157_372
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
context "a message with two int32 fields tagged #1 (optional, no default) and #2 (required)" do
|
259
|
+
setup do
|
260
|
+
fields = [Protopuffs::Int32.new("optional", "a", 1),
|
261
|
+
Protopuffs::Int32.new("required", "b", 2)]
|
262
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
263
|
+
@message = Protopuffs::Message::Test1.new
|
264
|
+
end
|
265
|
+
|
266
|
+
should_encode_wire_format_from_fields [0x10, 0xBC, 0xCD, 0x09],
|
267
|
+
:b => 157_372
|
268
|
+
should_decode_wire_format_to_fields [0x10, 0xBC, 0xCD, 0x09],
|
269
|
+
:a => 0, :b => 157_372
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
context "a message with two int32 fields tagged #1 and #2, defined with #2 first" do
|
274
|
+
setup do
|
275
|
+
fields = [Protopuffs::Int32.new("required", "b", 2),
|
276
|
+
Protopuffs::Int32.new("required", "a", 1)]
|
277
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
278
|
+
@message = Protopuffs::Message::Test1.new
|
279
|
+
end
|
280
|
+
|
281
|
+
# should always encode with fields ordered by tag number, to take
|
282
|
+
# advantage of decoders that optimize for this case
|
283
|
+
should_encode_wire_format_from_fields [0x08, 0x14, 0x10, 0xBC, 0xCD, 0x09],
|
284
|
+
:a => 20, :b => 157_372
|
285
|
+
|
286
|
+
# the decoder should still support any field order, though
|
287
|
+
should_decode_wire_format_to_fields [0x08, 0x14, 0x10, 0xBC, 0xCD, 0x09],
|
288
|
+
:a => 20, :b => 157_372
|
289
|
+
should_decode_wire_format_to_fields [0x10, 0xBC, 0xCD, 0x09, 0x08, 0x14],
|
290
|
+
:a => 20, :b => 157_372
|
291
|
+
end
|
292
|
+
|
293
|
+
end
|