chrisk-protopuffs 0.2.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/LICENSE.txt +18 -0
- data/README.rdoc +3 -0
- data/VERSION.yml +4 -0
- data/lib/protopuffs/message/base.rb +90 -0
- data/lib/protopuffs/message/field.rb +226 -0
- data/lib/protopuffs/message/wire_type.rb +10 -0
- data/lib/protopuffs/parser/parser.rb +24 -0
- data/lib/protopuffs/parser/protocol_buffer.treetop +124 -0
- data/lib/protopuffs.rb +27 -0
- data/test/abstract_syntax_tree_test.rb +142 -0
- data/test/fixtures/proto/person.proto +5 -0
- data/test/message_base_test.rb +98 -0
- data/test/message_field_test.rb +34 -0
- data/test/parse_tree_test.rb +206 -0
- data/test/protopuffs_test.rb +36 -0
- data/test/test_helper.rb +71 -0
- data/test/wire_format_test.rb +231 -0
- metadata +105 -0
@@ -0,0 +1,98 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class MessageBaseTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context ".define_message_class using 'Person' and two fields" do
|
6
|
+
should "create a Message::Person class" do
|
7
|
+
fields = [Protopuffs::MessageField.new("optional", "string", "name", 1),
|
8
|
+
Protopuffs::MessageField.new("optional", "string", "address", 2)]
|
9
|
+
Protopuffs::Message::Base.define_message_class("Person", fields)
|
10
|
+
Protopuffs::Message::Person
|
11
|
+
end
|
12
|
+
|
13
|
+
should "create a class with accessors for each field" do
|
14
|
+
fields = [Protopuffs::MessageField.new("optional", "string", "name", 1),
|
15
|
+
Protopuffs::MessageField.new("optional", "string", "address", 2)]
|
16
|
+
Protopuffs::Message::Base.define_message_class("Person", fields)
|
17
|
+
person = Protopuffs::Message::Person.new
|
18
|
+
person.name = "Chris"
|
19
|
+
person.address = "61 Carmelita St."
|
20
|
+
assert_equal person.name, "Chris"
|
21
|
+
assert_equal person.address, "61 Carmelita St."
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
context ".define_message_class with fields that have duplicate tags" do
|
27
|
+
should "raise a Protopuffs::ParseError" do
|
28
|
+
fields = [Protopuffs::MessageField.new("optional", "int32", "name", 1),
|
29
|
+
Protopuffs::MessageField.new("optional", "string", "address", 1)]
|
30
|
+
assert_raises Protopuffs::ParseError do
|
31
|
+
Protopuffs::Message::Base.define_message_class("Person", fields)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
context ".define_message_class with fields that have invalid tags" do
|
38
|
+
should "raise a Protopuffs::ParseError when a tag is too large" do
|
39
|
+
fields = [Protopuffs::MessageField.new("optional", "int32", "name", 1),
|
40
|
+
Protopuffs::MessageField.new("optional", "string", "address", 536_870_912)]
|
41
|
+
assert_raises Protopuffs::ParseError do
|
42
|
+
Protopuffs::Message::Base.define_message_class("Person", fields)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
should "raise a Protopuffs::ParseError when a tag is too small" do
|
47
|
+
fields = [Protopuffs::MessageField.new("optional", "int32", "name", 0),
|
48
|
+
Protopuffs::MessageField.new("optional", "string", "address", 1)]
|
49
|
+
assert_raises Protopuffs::ParseError do
|
50
|
+
Protopuffs::Message::Base.define_message_class("Person", fields)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
should "raise a Protopuffs::ParseError when a tag is reserved" do
|
55
|
+
fields = [Protopuffs::MessageField.new("optional", "string", "name", 19050)]
|
56
|
+
assert_raises Protopuffs::ParseError do
|
57
|
+
Protopuffs::Message::Base.define_message_class("Person", fields)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
should "not allow you to instantiate Message::Base directly" do
|
64
|
+
assert_raises RuntimeError do
|
65
|
+
Protopuffs::Message::Base.new
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
context "comparing messages with #==" do
|
71
|
+
should "return false when the messages have different types" do
|
72
|
+
Protopuffs::Message::Base.define_message_class("Dog", [])
|
73
|
+
Protopuffs::Message::Base.define_message_class("Cat", [])
|
74
|
+
assert_not_equal Protopuffs::Message::Dog.new, Protopuffs::Message::Cat.new
|
75
|
+
end
|
76
|
+
|
77
|
+
should "return false when the messages' fields have different values" do
|
78
|
+
fields = [Protopuffs::MessageField.new("optional", "string", "name", 1)]
|
79
|
+
Protopuffs::Message::Base.define_message_class("Person", fields)
|
80
|
+
alice = Protopuffs::Message::Person.new
|
81
|
+
alice.name = "Alice"
|
82
|
+
bob = Protopuffs::Message::Person.new
|
83
|
+
bob.name = "Bob"
|
84
|
+
assert_not_equal alice, bob
|
85
|
+
end
|
86
|
+
|
87
|
+
should "return true when messages of the same type have the same field values" do
|
88
|
+
fields = [Protopuffs::MessageField.new("optional", "string", "name", 1)]
|
89
|
+
Protopuffs::Message::Base.define_message_class("Sheep", fields)
|
90
|
+
sheep = Protopuffs::Message::Sheep.new
|
91
|
+
sheep.name = "Dolly"
|
92
|
+
sheep2 = Protopuffs::Message::Sheep.new
|
93
|
+
sheep2.name = "Dolly"
|
94
|
+
assert_equal sheep, sheep2
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class MessageFieldTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "creating a MessageField" do
|
6
|
+
should "set a string's default to '' when a default isn't specified" do
|
7
|
+
field = Protopuffs::MessageField.new("optional", "string", "name", 1)
|
8
|
+
assert_equal "", field.default
|
9
|
+
end
|
10
|
+
|
11
|
+
should "set a numeric's default to 0 when a default isn't specified" do
|
12
|
+
numeric_types = %w(double float int32 int64 uint32 unit64 sint32 sint64
|
13
|
+
fixed32 fixed64 sfixed32 sfixed64)
|
14
|
+
numeric_types.each do |type|
|
15
|
+
assert_equal 0, Protopuffs::MessageField.new("optional", type, "number", 1).default
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
should "set a bool's default to false when a default isn't specified" do
|
20
|
+
field = Protopuffs::MessageField.new("optional", "bool", "opt_in", 1)
|
21
|
+
assert_same false, field.default
|
22
|
+
end
|
23
|
+
|
24
|
+
should "set the default to 'Matz' when that default is specified" do
|
25
|
+
field = Protopuffs::MessageField.new("optional", "string", "name", 1, "Matz")
|
26
|
+
assert_equal "Matz", field.default
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
should_return_wire_type_for_fields_typed 0 => %w(int32 int64 uint32 uint64 bool),
|
31
|
+
1 => %w(double fixed64),
|
32
|
+
2 => %w(bytes string TestMessage),
|
33
|
+
5 => %w(float fixed32)
|
34
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class ParseTreeTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "a protocol buffer descriptor" do
|
6
|
+
setup do
|
7
|
+
@parser = Protopuffs::Parser::ProtocolBufferDescriptor.new
|
8
|
+
end
|
9
|
+
|
10
|
+
context "with an empty Person message" do
|
11
|
+
setup do
|
12
|
+
@proto = @parser.parse("message Person {}")
|
13
|
+
end
|
14
|
+
|
15
|
+
should "have one message" do
|
16
|
+
assert_equal 1, @proto.messages.size
|
17
|
+
end
|
18
|
+
|
19
|
+
should "have a message named Person" do
|
20
|
+
assert_equal "Person", @proto.messages.first.name
|
21
|
+
end
|
22
|
+
|
23
|
+
should "have an empty body" do
|
24
|
+
assert @proto.messages.first.body.empty?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "with two empty messages Apple and Orange" do
|
29
|
+
setup do
|
30
|
+
@descriptor = <<-proto
|
31
|
+
message Apple {}
|
32
|
+
message Orange {}
|
33
|
+
proto
|
34
|
+
end
|
35
|
+
|
36
|
+
should "have two messages" do
|
37
|
+
proto = @parser.parse(@descriptor)
|
38
|
+
assert_equal 2, proto.messages.size
|
39
|
+
end
|
40
|
+
|
41
|
+
should "have messages named Apple and Orange" do
|
42
|
+
proto = @parser.parse(@descriptor)
|
43
|
+
assert_equal %w(Apple Orange), proto.messages.map { |m| m.name }.sort
|
44
|
+
end
|
45
|
+
|
46
|
+
should "have messages with empty bodes" do
|
47
|
+
proto = @parser.parse(@descriptor)
|
48
|
+
assert proto.messages.all? { |m| m.body.empty? }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "a proto with a Person message including a name field" do
|
53
|
+
setup do
|
54
|
+
@descriptor = <<-proto
|
55
|
+
message Person {
|
56
|
+
required string name = 1;
|
57
|
+
}
|
58
|
+
proto
|
59
|
+
end
|
60
|
+
|
61
|
+
should "have one message named person" do
|
62
|
+
proto = @parser.parse(@descriptor)
|
63
|
+
assert_equal 1, proto.messages.size
|
64
|
+
assert_equal "Person", proto.messages.first.name
|
65
|
+
end
|
66
|
+
|
67
|
+
should "have one required string field called name with tag 1" do
|
68
|
+
proto = @parser.parse(@descriptor)
|
69
|
+
fields = proto.messages.first.body.fields
|
70
|
+
assert_equal 1, fields.size
|
71
|
+
assert_equal "required", fields.first.modifier.text_value
|
72
|
+
assert_equal "string", fields.first.type.text_value
|
73
|
+
assert_equal "name", fields.first.identifier.text_value
|
74
|
+
assert_equal "1", fields.first.integer.text_value
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "with a Person message including three fields" do
|
79
|
+
setup do
|
80
|
+
@descriptor = <<-proto
|
81
|
+
message Person {
|
82
|
+
required string name = 1;
|
83
|
+
required int32 id = 2;
|
84
|
+
optional string email = 3;
|
85
|
+
}
|
86
|
+
proto
|
87
|
+
end
|
88
|
+
|
89
|
+
should "have one message named person" do
|
90
|
+
proto = @parser.parse(@descriptor)
|
91
|
+
assert_equal 1, proto.messages.size
|
92
|
+
assert_equal "Person", proto.messages.first.name
|
93
|
+
end
|
94
|
+
|
95
|
+
should "have three fields with correct components" do
|
96
|
+
proto = @parser.parse(@descriptor)
|
97
|
+
fields = proto.messages.first.body.fields
|
98
|
+
assert_equal 3, fields.size
|
99
|
+
actual = fields.map { |f| [f.modifier, f.type, f.identifier, f.integer].map { |el| el.text_value } }
|
100
|
+
expected = [ %w(required string name 1),
|
101
|
+
%w(required int32 id 2),
|
102
|
+
%w(optional string email 3) ]
|
103
|
+
assert_equal expected, actual
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "with a Person message including two fields with defaults and one without" do
|
108
|
+
setup do
|
109
|
+
@proto = @parser.parse(<<-proto)
|
110
|
+
message Person {
|
111
|
+
required string name = 1;
|
112
|
+
optional string language = 2 [default = "en"];
|
113
|
+
optional int32 account_code = 3 [default = 0];
|
114
|
+
}
|
115
|
+
proto
|
116
|
+
end
|
117
|
+
|
118
|
+
should "have one message named person" do
|
119
|
+
assert_equal 1, @proto.messages.size
|
120
|
+
assert_equal "Person", @proto.messages.first.name
|
121
|
+
end
|
122
|
+
|
123
|
+
should "have three fields with correct components" do
|
124
|
+
fields = @proto.messages.first.body.fields
|
125
|
+
assert_equal 3, fields.size
|
126
|
+
actual = fields.map { |f| [f.modifier, f.type, f.identifier, f.integer, f.default] }
|
127
|
+
actual.map! { |f| f.map! { |el| el.respond_to?(:text_value) ? el.text_value : el } }
|
128
|
+
expected = [ ["required", "string", "name", "1", nil],
|
129
|
+
["optional", "string", "language", "2", "en"],
|
130
|
+
["optional", "int32", "account_code", "3", 0] ]
|
131
|
+
assert_equal expected, actual
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context "with a message including lots of comments" do
|
136
|
+
setup do
|
137
|
+
@proto = @parser.parse(<<-proto)
|
138
|
+
// test
|
139
|
+
//test
|
140
|
+
message Person { //test // test
|
141
|
+
// test
|
142
|
+
required string name = 1; // test
|
143
|
+
// optional string language = 2 [default = "en"];
|
144
|
+
optional int32 account_code = 3 [default = 0]; // test
|
145
|
+
// test
|
146
|
+
} // test
|
147
|
+
// test
|
148
|
+
//test
|
149
|
+
proto
|
150
|
+
end
|
151
|
+
|
152
|
+
should "parse" do
|
153
|
+
assert_not_nil @proto
|
154
|
+
end
|
155
|
+
|
156
|
+
should "have uncommented fields with correct components" do
|
157
|
+
fields = @proto.messages.first.body.fields
|
158
|
+
assert_equal 2, fields.size
|
159
|
+
actual = fields.map { |f| [f.modifier, f.type, f.identifier, f.integer, f.default] }
|
160
|
+
actual.map! { |f| f.map! { |el| el.respond_to?(:text_value) ? el.text_value : el } }
|
161
|
+
expected = [ ["required", "string", "name", "1", nil],
|
162
|
+
["optional", "int32", "account_code", "3", 0] ]
|
163
|
+
assert_equal expected, actual
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
context "with a message including a user-typed field" do
|
169
|
+
setup do
|
170
|
+
@proto = @parser.parse(<<-proto)
|
171
|
+
message Person {
|
172
|
+
required string name = 1;
|
173
|
+
repeated Address addresses = 2;
|
174
|
+
}
|
175
|
+
proto
|
176
|
+
end
|
177
|
+
|
178
|
+
should "parse" do
|
179
|
+
assert_not_nil @proto
|
180
|
+
end
|
181
|
+
|
182
|
+
should "have two fields with correct components" do
|
183
|
+
fields = @proto.messages.first.body.fields
|
184
|
+
assert_equal 2, fields.size
|
185
|
+
actual = fields.map { |f| [f.modifier, f.type, f.identifier, f.integer] }
|
186
|
+
actual.map! { |f| f.map! { |el| el.respond_to?(:text_value) ? el.text_value : el } }
|
187
|
+
expected = [ ["required", "string", "name", "1"],
|
188
|
+
["repeated", "Address", "addresses", "2"] ]
|
189
|
+
assert_equal expected, actual
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
should "raise a ParseError when parsing a message with a syntax error" do
|
195
|
+
assert_raises Protopuffs::ParseError do
|
196
|
+
@parser.parse(<<-proto)
|
197
|
+
message Person {
|
198
|
+
required name = 1
|
199
|
+
}
|
200
|
+
proto
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class ProtopuffsTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context ".proto_load_path accessors" do
|
6
|
+
setup { Protopuffs.proto_load_path = [] }
|
7
|
+
|
8
|
+
should "have an accessor for an array of load paths for .proto files" do
|
9
|
+
Protopuffs.proto_load_path << "proto_files" << "other_proto_files"
|
10
|
+
assert_equal ["proto_files", "other_proto_files"], Protopuffs.proto_load_path
|
11
|
+
end
|
12
|
+
|
13
|
+
should "have a mutator for directly assigning the load paths" do
|
14
|
+
Protopuffs.proto_load_path = ["my_proto_files"]
|
15
|
+
assert_equal ["my_proto_files"], Protopuffs.proto_load_path
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
should "have a ParseError class" do
|
20
|
+
Protopuffs::ParseError
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
context ".load_message_classes when a descriptor is in the load path with a Person message" do
|
25
|
+
setup { Protopuffs.proto_load_path = ["#{File.dirname(__FILE__)}/fixtures/proto"] }
|
26
|
+
|
27
|
+
should "create a Person message class with correct accessors" do
|
28
|
+
Protopuffs.load_message_classes
|
29
|
+
p = Protopuffs::Message::Person.new
|
30
|
+
p.name = "Chris"
|
31
|
+
p.id = 42
|
32
|
+
p.email = "chris@kampers.net"
|
33
|
+
assert_equal ["Chris", 42, "chris@kampers.net"], [p.name, p.id, p.email]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'shoulda'
|
4
|
+
require 'mocha'
|
5
|
+
require 'treetop'
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
8
|
+
require 'protopuffs'
|
9
|
+
|
10
|
+
class Test::Unit::TestCase
|
11
|
+
|
12
|
+
# helper for debugging
|
13
|
+
def print_bytes(string)
|
14
|
+
puts
|
15
|
+
string.each_byte do |byte|
|
16
|
+
printf("%1$08b (%1$02X)\n", byte)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# helper for generating test names
|
21
|
+
def self.inspect_bytes(bytes_array)
|
22
|
+
"[#{bytes_array.map { |b| sprintf("%02X", b) }.join(' ')}]"
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def self.should_encode_wire_format_from_fields(expected_bytes, actual_fields)
|
27
|
+
should "encode the fields #{actual_fields.inspect} to the byte string #{inspect_bytes(expected_bytes)}" do
|
28
|
+
actual_fields.each_pair do |name, value|
|
29
|
+
value = value.call if value.respond_to?(:call)
|
30
|
+
@message.send("#{name}=", value)
|
31
|
+
end
|
32
|
+
actual_bytes = @message.to_wire_format.unpack('C*')
|
33
|
+
assert_equal expected_bytes, actual_bytes
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.should_decode_wire_format_to_fields(actual_bytes, expected_fields)
|
38
|
+
should "decode the byte string #{inspect_bytes(actual_bytes)} to the fields #{expected_fields.inspect}" do
|
39
|
+
buffer = StringIO.new(actual_bytes.pack('C*'))
|
40
|
+
@message.from_wire_format(buffer)
|
41
|
+
actual_fields = @message.class.fields.inject({}) { |hash, field|
|
42
|
+
hash[field.identifier.to_sym] = @message.send(field.identifier)
|
43
|
+
hash
|
44
|
+
}
|
45
|
+
|
46
|
+
expected_fields.each_pair do |key, expected_value|
|
47
|
+
expected_value = expected_value.call if expected_value.respond_to?(:call)
|
48
|
+
if expected_value.is_a?(Float)
|
49
|
+
assert_in_delta(expected_value, actual_fields[key], Float::EPSILON)
|
50
|
+
else
|
51
|
+
assert_equal expected_value, actual_fields[key]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
assert_equal expected_fields.size, actual_fields.size
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def self.should_return_wire_type_for_fields_typed(wire_types)
|
61
|
+
wire_types.each_pair do |wire_type, names|
|
62
|
+
names.each do |name|
|
63
|
+
should "return wire type #{wire_type} for a field with type #{name}" do
|
64
|
+
assert_equal wire_type, Protopuffs::MessageField.new("required", name, "a", 1).wire_type
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
@@ -0,0 +1,231 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class WireFormatTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "a message with one int32 field tagged #1" do
|
6
|
+
# from http://code.google.com/apis/protocolbuffers/docs/encoding.html#simple
|
7
|
+
setup do
|
8
|
+
fields = [Protopuffs::MessageField.new("required", "int32", "a", 1)]
|
9
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
10
|
+
@message = Protopuffs::Message::Test1.new
|
11
|
+
end
|
12
|
+
|
13
|
+
should_encode_wire_format_from_fields [0x08, 0x96, 0x01], :a => 150
|
14
|
+
should_decode_wire_format_to_fields [0x08, 0x96, 0x01], :a => 150
|
15
|
+
|
16
|
+
# should ignore unknown fields: this message also has an int32 tagged #2 with value 157,372
|
17
|
+
should_decode_wire_format_to_fields [0x08, 0x96, 0x01, 0x10, 0xBC, 0xCD, 0x09], :a => 150
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
context "a message with two int32 fields tagged #1 and #2" do
|
22
|
+
setup do
|
23
|
+
fields = [Protopuffs::MessageField.new("required", "int32", "a", 1),
|
24
|
+
Protopuffs::MessageField.new("required", "int32", "b", 2)]
|
25
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
26
|
+
@message = Protopuffs::Message::Test1.new
|
27
|
+
end
|
28
|
+
|
29
|
+
should_encode_wire_format_from_fields [0x08, 0x96, 0x01, 0x10, 0xBC, 0xCD, 0x09],
|
30
|
+
:a => 150, :b => 157_372
|
31
|
+
should_decode_wire_format_to_fields [0x08, 0x96, 0x01, 0x10, 0xBC, 0xCD, 0x09],
|
32
|
+
:a => 150, :b => 157_372
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
context "a message with one int64 field tagged #1" do
|
37
|
+
setup do
|
38
|
+
fields = [Protopuffs::MessageField.new("required", "int64", "a", 1)]
|
39
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
40
|
+
@message = Protopuffs::Message::Test1.new
|
41
|
+
end
|
42
|
+
|
43
|
+
should_encode_wire_format_from_fields [0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02],
|
44
|
+
:a => 2**50
|
45
|
+
should_decode_wire_format_to_fields [0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02],
|
46
|
+
:a => 2**50
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
context "a message with one uint32 field tagged #1 and one uint64 field tagged #2" do
|
51
|
+
setup do
|
52
|
+
fields = [Protopuffs::MessageField.new("required", "uint32", "a", 1),
|
53
|
+
Protopuffs::MessageField.new("required", "uint64", "b", 2)]
|
54
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
55
|
+
@message = Protopuffs::Message::Test1.new
|
56
|
+
end
|
57
|
+
|
58
|
+
should_encode_wire_format_from_fields [0x08, 0x90, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x20],
|
59
|
+
:a => 912, :b => 2**54
|
60
|
+
should_decode_wire_format_to_fields [0x08, 0x90, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x20],
|
61
|
+
:a => 912, :b => 2**54
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
context "a message with two bool fields tagged #1 and #2" do
|
66
|
+
setup do
|
67
|
+
fields = [Protopuffs::MessageField.new("required", "bool", "a", 1),
|
68
|
+
Protopuffs::MessageField.new("required", "bool", "b", 2)]
|
69
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
70
|
+
@message = Protopuffs::Message::Test1.new
|
71
|
+
end
|
72
|
+
|
73
|
+
should_encode_wire_format_from_fields [0x08, 0x00, 0x10, 0x01],
|
74
|
+
:a => false, :b => true
|
75
|
+
should_decode_wire_format_to_fields [0x08, 0x00, 0x10, 0x01],
|
76
|
+
:a => false, :b => true
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
context "a message with one string field tagged #2" do
|
81
|
+
setup do
|
82
|
+
# from http://code.google.com/apis/protocolbuffers/docs/encoding.html#types
|
83
|
+
fields = [Protopuffs::MessageField.new("required", "string", "b", 2)]
|
84
|
+
Protopuffs::Message::Base.define_message_class("Test2", fields)
|
85
|
+
@message = Protopuffs::Message::Test2.new
|
86
|
+
end
|
87
|
+
|
88
|
+
should_encode_wire_format_from_fields [0x12, 0x07, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6E, 0x67],
|
89
|
+
:b => "testing"
|
90
|
+
should_decode_wire_format_to_fields [0x12, 0x07, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6E, 0x67],
|
91
|
+
:b => "testing"
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
context "a message with a bytes field tagged #1" do
|
96
|
+
setup do
|
97
|
+
fields = [Protopuffs::MessageField.new("required", "bytes", "a", 1)]
|
98
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
99
|
+
@message = Protopuffs::Message::Test1.new
|
100
|
+
end
|
101
|
+
|
102
|
+
should_encode_wire_format_from_fields [0x0A, 0x04, 0xDE, 0xCA, 0xFB, 0xAD],
|
103
|
+
:a => [0xDE, 0xCA, 0xFB, 0xAD].pack('C*')
|
104
|
+
should_decode_wire_format_to_fields [0x0A, 0x04, 0xDE, 0xCA, 0xFB, 0xAD],
|
105
|
+
:a => [0xDE, 0xCA, 0xFB, 0xAD].pack('C*')
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
context "a message with one float field tagged #1" do
|
110
|
+
setup do
|
111
|
+
fields = [Protopuffs::MessageField.new("required", "float", "a", 1)]
|
112
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
113
|
+
@message = Protopuffs::Message::Test1.new
|
114
|
+
end
|
115
|
+
|
116
|
+
# 1.61803 gives you repeating binary digits when encoded, so the number
|
117
|
+
# you get from decoding is different (within Float::EPSILON)
|
118
|
+
should_encode_wire_format_from_fields [0x0D, 0x9B, 0x1B, 0xCF, 0x3F],
|
119
|
+
:a => 1.61803
|
120
|
+
should_decode_wire_format_to_fields [0x0D, 0x9B, 0x1B, 0xCF, 0x3F],
|
121
|
+
:a => 1.6180299520492554
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
context "a message with one double field tagged #1" do
|
126
|
+
setup do
|
127
|
+
fields = [Protopuffs::MessageField.new("required", "double", "a", 1)]
|
128
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
129
|
+
@message = Protopuffs::Message::Test1.new
|
130
|
+
end
|
131
|
+
|
132
|
+
# 64-bit doubles have enough precision to encode/decode 1.61803,
|
133
|
+
# unlike the 32-bit floats above
|
134
|
+
should_encode_wire_format_from_fields [0x09, 0x6C, 0x26, 0xDF, 0x6C, 0x73, 0xE3, 0xF9, 0x3F],
|
135
|
+
:a => 1.61803
|
136
|
+
should_decode_wire_format_to_fields [0x09, 0x6C, 0x26, 0xDF, 0x6C, 0x73, 0xE3, 0xF9, 0x3F],
|
137
|
+
:a => 1.61803
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
context "a message with one fixed64 field tagged #1" do
|
142
|
+
setup do
|
143
|
+
fields = [Protopuffs::MessageField.new("required", "fixed64", "a", 1)]
|
144
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
145
|
+
@message = Protopuffs::Message::Test1.new
|
146
|
+
end
|
147
|
+
|
148
|
+
should_encode_wire_format_from_fields [0x09, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F],
|
149
|
+
:a => 2**62 - 15
|
150
|
+
should_decode_wire_format_to_fields [0x09, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F],
|
151
|
+
:a => 2**62 - 15
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
context "a message with one fixed32 field tagged #1" do
|
157
|
+
setup do
|
158
|
+
fields = [Protopuffs::MessageField.new("required", "fixed32", "a", 1)]
|
159
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
160
|
+
@message = Protopuffs::Message::Test1.new
|
161
|
+
end
|
162
|
+
|
163
|
+
should_encode_wire_format_from_fields [0x0D, 0xFE, 0xFF, 0xFF, 0xFF],
|
164
|
+
:a => 2**32 - 2
|
165
|
+
should_decode_wire_format_to_fields [0x0D, 0xFE, 0xFF, 0xFF, 0xFF],
|
166
|
+
:a => 2**32 - 2
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
context "a message with one repeating int32 field tagged #1" do
|
171
|
+
setup do
|
172
|
+
fields = [Protopuffs::MessageField.new("repeated", "int32", "a", 1)]
|
173
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
174
|
+
@message = Protopuffs::Message::Test1.new
|
175
|
+
end
|
176
|
+
|
177
|
+
should_encode_wire_format_from_fields [0x08, 0x96, 0x01, 0x08, 0xBC, 0xCD, 0x09, 0x08, 0x3D],
|
178
|
+
:a => [150, 157_372, 61]
|
179
|
+
should_decode_wire_format_to_fields [0x08, 0x96, 0x01, 0x08, 0xBC, 0xCD, 0x09, 0x08, 0x3D],
|
180
|
+
:a => [150, 157_372, 61]
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
context "a message with one embedded-message field Test1 tagged #3 (where Test1 has an int32 field tagged #1)" do
|
185
|
+
# from http://code.google.com/apis/protocolbuffers/docs/encoding.html#embedded
|
186
|
+
setup do
|
187
|
+
test1_fields = [Protopuffs::MessageField.new("required", "int32", "a", 1)]
|
188
|
+
Protopuffs::Message::Base.define_message_class("Test1", test1_fields)
|
189
|
+
|
190
|
+
test3_fields = [Protopuffs::MessageField.new("required", "Test1", "c", 3)]
|
191
|
+
Protopuffs::Message::Base.define_message_class("Test3", test3_fields)
|
192
|
+
@message = Protopuffs::Message::Test3.new
|
193
|
+
end
|
194
|
+
|
195
|
+
should_encode_wire_format_from_fields [0x1A, 0x03, 0x08, 0x96, 0x01],
|
196
|
+
:c => lambda { msg = Protopuffs::Message::Test1.new; msg.a = 150; msg }
|
197
|
+
should_decode_wire_format_to_fields [0x1A, 0x03, 0x08, 0x96, 0x01],
|
198
|
+
:c => lambda { msg = Protopuffs::Message::Test1.new; msg.a = 150; msg }
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
context "a message with two int32 fields tagged #1 (optional, default=150) and #2 (required)" do
|
203
|
+
setup do
|
204
|
+
fields = [Protopuffs::MessageField.new("optional", "int32", "a", 1, 150),
|
205
|
+
Protopuffs::MessageField.new("required", "int32", "b", 2)]
|
206
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
207
|
+
@message = Protopuffs::Message::Test1.new
|
208
|
+
end
|
209
|
+
|
210
|
+
should_encode_wire_format_from_fields [0x10, 0xBC, 0xCD, 0x09],
|
211
|
+
:b => 157_372
|
212
|
+
should_decode_wire_format_to_fields [0x10, 0xBC, 0xCD, 0x09],
|
213
|
+
:a => 150, :b => 157_372
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
context "a message with two int32 fields tagged #1 (optional, no default) and #2 (required)" do
|
218
|
+
setup do
|
219
|
+
fields = [Protopuffs::MessageField.new("optional", "int32", "a", 1),
|
220
|
+
Protopuffs::MessageField.new("required", "int32", "b", 2)]
|
221
|
+
Protopuffs::Message::Base.define_message_class("Test1", fields)
|
222
|
+
@message = Protopuffs::Message::Test1.new
|
223
|
+
end
|
224
|
+
|
225
|
+
should_encode_wire_format_from_fields [0x10, 0xBC, 0xCD, 0x09],
|
226
|
+
:b => 157_372
|
227
|
+
should_decode_wire_format_to_fields [0x10, 0xBC, 0xCD, 0x09],
|
228
|
+
:a => 0, :b => 157_372
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|